Javascript Node.js循环异步调用

Javascript Node.js循环异步调用,javascript,node.js,mongodb,asynchronous,Javascript,Node.js,Mongodb,Asynchronous,我正忙于报告系统的端点。节点异步给我带来了问题,尽管我不想强制它同步 我们正在使用MongoDB和Mongoose。我必须在集合A上查询正则表达式,然后对于返回的每个文档,查询多个包含的文档以填充要返回的JSON对象/数组 我可以对大多数数据使用populate,但最后的循环查询除外,该查询是异步启动并提前返回报告的地方。有没有一种优雅的方法可以做到这一点?或者我应该拆分成一个不同的函数并多次调用该函数以坚持函数应该只做一件事规则吗 示例代码: A.find({ name: regex }).p

我正忙于报告系统的端点。节点异步给我带来了问题,尽管我不想强制它同步

我们正在使用MongoDB和Mongoose。我必须在集合A上查询正则表达式,然后对于返回的每个文档,查询多个包含的文档以填充要返回的JSON对象/数组

我可以对大多数数据使用
populate
,但最后的循环查询除外,该查询是异步启动并提前返回报告的地方。有没有一种优雅的方法可以做到这一点?或者我应该拆分成一个不同的函数并多次调用该函数以坚持
函数应该只做一件事
规则吗

示例代码:

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;
        });
这里的问题是逻辑/异步。不使用语法,因此使用半伪代码


任何帮助或建议都将不胜感激。

通常在异步调用中,在return语句之后,任何操作都将被取消

也许只有当所有的工作都完成并且很好的时候,您才能返回报表对象

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;
        });
只需使用承诺:

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)
   })

您需要熟悉承诺和异步。 因为您要返回一个数组,所以这就是您将要获得的值

在处理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);
        }
      }
    });
  });
});
注意,我没有返回
内容
,而是将其传递到回调中

您可以使用高阶函数来清理这类内容

// 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));
});
注意,除了高阶函数之外,我从不返回任何东西,我总是传递continuations(下一步要做的事情,带有一个值)

另一种方法是承诺

// Promises
getSetOfIDs()
  .then(ids => Promise.all(ids.map(getThingByID)))
  .then(things => app.display(things))
  .catch(err => app.apologize(err));
要真正了解这里的情况,请学习Promises、Promise.all静态方法和
array.map()


理论上,这两组代码的作用完全相同,只是在最后一种情况下,
getSetOfIDs
getThingByID
不接受回调,而是返回承诺。

在这段代码中,报告将在返回的C文档的第一次迭代后返回?哦,你是对的,但是你知道在哪里返回报告对象,我会更正答案这是我遇到的问题。如果我在函数末尾返回
report
,它将返回空。这是因为只要
aggregate
函数启动,async就会启动并继续处理当前函数。这最终会在填充报表之前命中return语句。我遇到了问题,在这种情况下,一个技巧是将异步返回赋值给对象值。另一种方法是使用承诺。我会看看承诺这个解决方案会发生几次吗(从而以1个元素而不是0结束)?你是对的,我只是更改了多个承诺