2017-01-31 2 views
1

Я занят работой над конечной точкой для системы отчетности. Узел async дает мне проблемы, хотя я бы не хотел, чтобы он был синхронным.Node.js looping async calls

Мы используем MongoDB и Mongoose. Мне нужно запросить regex над сборкой A, затем для каждого возвращаемого документа запросите несколько документов, чтобы заполнить возвращаемый объект/массив JSON.

Для большинства данных я могу использовать populate, за исключением окончательных запросов с петлями, в которых асинхронно запускается и возвращает мой отчет раньше. Есть ли элегантный способ сделать это? Или я должен раскалываться на другую функцию и называть это несколько раз, чтобы придерживаться правила functions should do only one thing?

Пример кода:

A.find({ name: regex }).populate({ path: 'B', populate: { path: 'B.C', model: 'C' } }).exec(function(err, A) { 
      var report = []; 

      A.map(function(a)){ 
       report[a.name] = []; 

       D.aggregate([ 
        { 
         $match: { 
          id: B._id 
         } 
        }, 
        { 
         $group: { 
          _id: null, 
          count: { $sum: 1 } 
         } 
        } 
       ], function(err, result) { 
        C.map(function(c){ 
         report[a.name].push({ 
          'field1': c.field1, 
          'field2': c.field2, 
          'field3': c.field3, 
          'count': result.count 
         }); 
        });       
       }); 
      } 

      return report; 
     }); 

Проблема здесь с логикой/асинхронном. Не с синтаксисом, следовательно, с полу-псевдокодом.

Любая помощь или совет были бы очень признательны.

ответ

0

обычно в асинхронных вызовах, после операции возврата любые операции отменены.

Возможно, вы можете вернуть объект отчета только тогда, когда все сделано и хорошо.

A.find({ name: regex }).populate({ path: 'B', populate: { path: 'B.C', model: 'C' } }).exec(function(err, A) { 
      var report = []; 

      A.map(function(a)){ 
       report[a.name] = D.aggregate([ 
        { 
         $match: { 
          id: B._id 
         } 
        }, 
        { 
         $group: { 
          _id: null, 
          count: { $sum: 1 } 
         } 
        } 
       ], function(err, result) { 
        if(err){ 
         return []; 
        } 
        var fields = [] 
        C.map(function(c){ 
         fields.push({ 
          'field1': c.field1, 
          'field2': c.field2, 
          'field3': c.field3, 
          'count': result.count 
         }); 
        }); 
        return fields;      
       });  
      } 
      return report; 
     }); 
+0

В этом коде отчет будет возвращен после первой итерации документов C. – nickcorin

+0

К сожалению, вы правы, но знаете, где вернуть объект отчета, я бы исправил ответ –

+0

В этом проблема, которую я испытываю. Если я верну 'отчет' в конце функции, он будет пустым. Это связано с тем, что, как только запускается функция 'aggregate', async запускает и продолжает обрабатывать текущую функцию. Это приводит к удалению оператора return перед заполнением отчета. – nickcorin

0

Просто используйте обещание:

A.find({ name: regex }).populate({ path: 'B', populate: { path: 'B.C', model: 'C' } }).exec(function(err, A) { 

    var report = []; 
    return Promise.all([ 
     A.map(function(a)){ 
     return new Promise(function(resolve, reject) { 

      report[a.name] = []; 

      D.aggregate([{ $match: { id: B._id }},{$group: {_id: null,count: { $sum: 1 }}}], 
       function(err, result) { 
       if(err) { 
        reject(err) 
       } else { 
        C.map(function(c){ 
        report[a.name].push({ 
         'field1': c.field1, 
         'field2': c.field2, 
         'field3': c.field3, 
         'count': result.count 
        }); 
        }); 
        resolve(report) 
       } 
      }); 
     } 
    })]) 
    }) 
    .then(function(report){ 
    console.log(report) 
    }) 
    .catch(function(err){ 
    console.log(err) 
    }) 
+0

Разве это решение произойдет несколько раз (и, таким образом, закончить с 1 элементом, а не 0)? – Norguard

+0

Вы правы, я просто изменился для нескольких обещаний –

2

Вы должны ознакомиться с обещаниями, и с асинхронномом в целом. Поскольку вы возвращаете массив, это значение, которое вы получите.

У вас есть несколько вариантов, когда дело с Async, но в вашем случае, вы хотите посмотреть на двух решений:

// callbacks 
getSetOfIDs((err, ids) => { 
    let remaining = ids.length; 
    let things = []; 
    let failed = false; 

    ids.forEach(id => { 
    getThingByID(id, (err, thing) => { 
     if (failed) { return; } 
     if (err) { 
     failed = true; 
     handleFailure(err); 
     } else { 
     remaining -= 1; 
     things.push(thing); 
     if (!remaining) { 
      handleSuccess(things); 
     } 
     } 
    }); 
    }); 
}); 

Обратите внимание, я не вернувшихся things, я передаю его в Перезвони.

Вы можете использовать функции более высокого порядка для очистки такого рода вещей.

// cleaned up callbacks 
function handleNodeCallback (succeed, fail) { 
    return function (err, data) { 
    if (err) { 
     fail(err); 
    } else { 
     succeed(data); 
    } 
    }; 
} 

function handleAggregateCallback (succeed, fail, count) { 
    let items = []; 
    let failed = false; 

    const ifNotFailed = cb => data => { 
    if (!failed) { cb(data); } 
    }; 

    const handleSuccess = ifNotFailed((item) => { 
    items.push(item); 
    if (items.length === count) { succeed(items); } 
    }); 

    const handleFailure = ifNotFailed((err) => { 
    failed = true; 
    fail(err); 
    }); 

    return handleNodeCallback(handleSuccess, handleFailure); 
} 

Маленький помощник код позже, и мы готовы пойти:

// refactored callback app code (note that it's much less scary) 
getSetOfIDs((err, ids) => { 
    const succeed = (things) => app.display(things); 
    const fail = err => app.apologize(err); 
    if (err) { return fail(err); } 

    let onThingResponse = handleAggregateCallback(succeed, fail, ids.length); 
    ids.forEach(id => getThingByID(id, onThingResponse)); 
}); 

Обратите внимание, что помимо функций высшего порядка, я никогда не возвращающееся ничего, я всегда прохожу продолжения (что делать дальше, со значением).

Другой метод Обещание

// Promises 
getSetOfIDs() 
    .then(ids => Promise.all(ids.map(getThingByID))) 
    .then(things => app.display(things)) 
    .catch(err => app.apologize(err)); 

Для того чтобы действительно получить то, что здесь происходит, узнать Обещание, статический метод Promise.all и array.map().

Оба эти набора кода теоретически выполняют то же самое, за исключением того, что в этом последнем случае getSetOfIDs и getThingByID не принимают обратные вызовы, вместо этого они вместо этого возвращают обещания.