【问题标题】:Node.js looping async callsNode.js 循环异步调用
【发布时间】:2017-06-16 14:59:37
【问题描述】:

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

我们正在使用 MongoDB 和 Mongoose。我必须查询集合 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;
        });

这里的问题在于逻辑/异步。不是语法,因此是半伪代码。

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

【问题讨论】:

    标签: javascript node.js mongodb asynchronous


    【解决方案1】:

    通常在异步调用中,在 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;
            });
    

    【讨论】:

    • 这段代码中,返回的C文档第一次迭代后会返回report?
    • 哎呀,你是对的,但你知道在哪里返回报告对象,我会更正答案
    • 这就是我遇到的问题。如果我在函数末尾返回report,它返回空。这是因为只要aggregate 函数启动,异步就会启动并继续处理当前函数。这最终会在填充报告之前命中 return 语句。
    • 我遇到了问题,在这种情况下,一个技巧是将异步返回分配给对象值。其他方法是使用承诺。
    • 我来看看 Promise。 :)
    【解决方案2】:

    只需使用承诺:

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

    【讨论】:

    • 该解析是否会发生多次(因此以 1 个元素而不是 0 个元素结束)?
    • 你说得对,我只是换了多个 promises
    【解决方案3】:

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

    在处理 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

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

    要真正了解这里发生了什么,请学习 Promises、Promise.all 静态方法和 array.map()

    这两组代码理论上做的事情完全相同,只是在最后一种情况下 getSetOfIDsgetThingByID 不接受回调,而是返回 Promise。

    【讨论】:

      猜你喜欢
      • 2013-02-16
      • 1970-01-01
      • 2015-04-11
      • 1970-01-01
      • 2011-11-08
      • 2015-02-27
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      相关资源
      最近更新 更多