简体   繁体   English

Node.js循环异步调用

[英]Node.js looping async calls

I'm busy working on an endpoint for a reporting system. 我正在为报告系统的端点工作。 Node being async is giving me issues, although I'd rather not force it to be synchronous. 节点异步给了我一些问题,尽管我不想强迫它同步。

We're using MongoDB and Mongoose. 我们正在使用MongoDB和Mongoose。 I've got to query regex over collection A, then for each document that gets returned, query multiple contained documents to populate a JSON object/array to be returned. 我必须查询集合A的正则表达式,然后对于每个返回的文档,查询多个包含的文档以填充要返回的JSON对象/数组。

I can use populate for most of the data, except the final looped queries which is where the async kicks in and returns my report early. 除了最终循环查询(异步启动并提前返回报告的地方)外,我可以对大多数数据使用populate Is there an elegant way to do this? 有没有一种优雅的方法可以做到这一点? Or should I be splitting into a different function and calling that multiple times to stick to the functions should do only one thing rule? 还是我应该拆分为一个不同的函数,并多次调用该函数来坚持做这些functions should do only one thing

Example Code: 示例代码:

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

The issue here is with the logic / async. 这里的问题是逻辑/异步。 Not with the syntax, hence the semi-pseudo code. 不使用语法,因此不使用半伪代码。

Any help or advice would be greatly appreciated. 任何帮助或建议,将不胜感激。

You need to familiarize yourself with promises, and with async in general. 您需要使自己熟悉Promise和异步。 Because you are returning an array, that's the value you are going to get. 因为您要返回一个数组,所以这就是您要获得的值。

You have a few options when dealing with Async, but in your case, you want to look at two solutions: 在处理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);
        }
      }
    });
  });
});

Note, I'm not returning things , I'm passing it into a callback. 注意,我没有返回任何things ,而是将其传递给了回调。

You can use higher-order functions to clean this sort of thing up. 您可以使用高阶函数来清理此类事件。

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

A little helper code later, and we're ready to go: 稍后提供一些辅助代码,我们可以开始了:

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

Note that aside from higher-order functions, I'm never returning anything, I'm always passing continuations (things to do next, with a value). 请注意,除了高阶函数外,我从不返回任何东西,而是始终传递延续(接下来要做的事情,带有一个值)。

The other method is Promises 另一种方法是承诺

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

To really get what's going on here, learn Promises, the Promise.all static method, and array.map() . 要真正了解这里发生的事情,请学习Promises,Promise.all静态方法和array.map()

Both of these sets of code theoretically do the exact same thing, except that in this last case getSetOfIDs and getThingByID don't take callbacks, they return promises instead. 从理论上讲,这两组代码都做同样的事情,除了在最后一种情况下, getSetOfIDsgetThingByID不接受回调,它们返回的是getThingByID

usually in async calls, after return statement any operations are cancelled. 通常在异步调用中,在return语句之后,任何操作都将被取消。

maybe you can return report object only when all is done and well. 也许只有在一切都做好之后,您才能返回报告对象。

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

Just use promises: 只需使用诺言:

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

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM