Node.js:你如何处理循环中的回调?

pau*_*sm4 8 asynchronous for-loop node.js

我正在使用Node.js和Box SDK.我的(失败!)代码如下所示:

var connection = box.getConnection(req.user.login);
connection.ready(function () {
  connection.getFolderItems(0, null, function (err, result) {
    if (err) {
      opts.body = err;
    } else {
      opts.body = result;
      var a = [];
      for (var i=0; i < result.entries.length; i++) {
        connection.getFileInfo(result.entries[i].id, function (err, fileInfo) {
        if (err) {
          opts.body = err;
        } else {
          a.push(fileInfo);
        }
      });}
    }
Run Code Online (Sandbox Code Playgroud)

在"程序"术语中,这是我想要做的:

var connection= box.getConnection()
var items = connection.getFolderItems()
var itemList = new List
foreach (item in items) {
  connection.getFileInfo(item.id)
  itemList.add(item)
}
display(itemList)
Run Code Online (Sandbox Code Playgroud)

我的问题是,connection.getFolderItems()并且connection.getFileInfo()是异步的 - 在返回所有结果之前,"for"循环退出.

问:Node.js中最好的方法是1)获得第一个异步调用的结果,2)遍历列表,进行更多的异步调用,以及3)当所有内容都"完成"时处理结果.

问:承诺在这里是一个不错的选择吗?

问:完成()/ next()是一个选项吗?

问:对于这种情况,Node.js中是否有任何"标准习惯用法"?

ska*_*ace 11

Promise是一个好主意,但你可能想看看async模块,特别是集合处理程序.它允许您针对"事物"列表运行异步调用,并在完成所有异步调用时为您提供运行方法的位置.不知道这是否比承诺更好,但选择总是很好.

// Should give you a rough idea
async.each(items, function (item, callback) {
  connection.getFileInfo(result, callback);
}, function (err) {
  console.log('All done');
});
Run Code Online (Sandbox Code Playgroud)

https://github.com/caolan/async#each


jfr*_*d00 5

问:Node.js中最好的方法是1)获得第一个异步调用的结果,2)遍历列表,进行更多的异步调用,以及3)当所有内容都"完成"时处理结果.

有多种方法.手动编码,Promises,异步库."最好的"是旁观者的眼睛,所以我们不能在这里说.我使用Promises进行所有异步编码.它们已经在ES6中正式标准化,并且有很好的,强大的实现(我喜欢Bluebird的超出标准的额外功能,简化了复杂的异步任务,并且它的promisifyAll()功能为您提供了使用任何标准异步操作的承诺接口.异步回调调用约定).

我建议不要手工编码复杂的异步操作,因为强大的错误处理非常困难,异常可以在异步回调中静默处理,导致错误处理丢失和调试困难.Async库可能是最好的非Promise方式,因为它提供了围绕异步回调的一些基础架构和同步功能.

我个人更喜欢承诺.我认为随着时间的推移,我们会看到更多异步API标准化返回承诺,因此我认为这是一种更好的选择,可以用于前瞻性的学习和编程方式.

问:承诺在这里是一个不错的选择吗?

是的,promises允许你运行一堆异步操作,然后使用类似的东西Promise.all()知道它们什么时候完成.它还将为您收集所有异步操作的所有结果.

问:完成()/ next()是一个选项吗?

我不确定你在这里问的是什么,但你可以手动编写异步操作代码并行运行并知道它们何时完成或按顺序运行它们并知道它们何时完成.Promise为你做了很多这项工作,但你可以不用它们手动完成.

问:对于这种情况,Node.js中是否有任何"标准习惯用法"?

如果使用promises,会有一种常见的方法来执行此操作.如果不使用promises,可能没有"标准习语",因为有很多不同的方法可以自己编写代码.

承诺实施

这是使用node.js中的Bluebird Promise库的示例:

var Promise = require('bluebird');
var connection = Promise.promisifyAll(box.getConnection(req.user.login));
connection.ready(function() {
    connection.getFolderItemsAsync(0, null).then(function(result) {
        return Promise.map(result.entries, function(item) {
            return connection.getFileInfoAsync(item.id);
        })
    }).then(function(results) {
        // array of results here
    }, function(err) {
        // error here
    });
});
Run Code Online (Sandbox Code Playgroud)

这是如何工作的:

  1. Promisify连接对象,以便其所有方法都有一个返回promise的版本(只需在方法的末尾添加"Async"以调用此promisified版本).

  2. 呼叫getFolderItemsAsync()及其承诺将通过result.entries阵列解决

  3. 运行该数组的映射,并行运行所有操作并返回一个promise,当完成所有操作后,该promise将使用一系列有序结果进行解析.

  4. 每个条目的实际结果都是通过实现的connection.getFileInfoAsync().

  5. 创建解析处理程序和拒绝处理程序.如果进程中的任何位置发生任何错误,它将传播到拒绝处理程序.如果所有操作都成功,将使用有序的结果数组调用最后一个解析处理程序.

如果出现错误,则上述版本将中止,除了错误代码之外,您没有得到任何结果.如果您想要null在出现错误时继续查看结果,那么您可以使用以下内容:

var Promise = require('bluebird');
var connection = Promise.promisifyAll(box.getConnection(req.user.login));
connection.ready(function() {
    connection.getFolderItemsAsync(0, null).then(function(result) {
        return Promise.map(result.entries, function(item) {
            return connection.getFileInfoAsync(item.id).catch(function(err){
                // post the results as null and continue with the other results
                return null;
            });
        })
    }).then(function(results) {
        // array of results here (some might be null if they had an error)
    }, function(err) {
        // error here
    });
});
Run Code Online (Sandbox Code Playgroud)

手动编码版本

这是一个手动编码的版本.这个的关键是通过比较来检测异步循环何时完成if (results.length === result.entries.length).注意:这有不完整的错误处理,这是手动编码和不使用像promises这样的异步框架的困难之一.

var connection = box.getConnection(req.user.login);
connection.ready(function () {
    connection.getFolderItems(0, null, function (err, result) {
        if (err) {
            // do error handling here
            opts.body = err;
        } else {
            var results = [];
            for (var i = 0; i < result.entries.length; i++) {
                connection.getFileInfo(result.entries[i].id, function (err, fileInfo) {
                    if (err) {
                        // do error handling here
                        opts.body = err;
                        results.push(null);
                    } else {
                        results.push(fileInfo);
                    }
                    // if done with all requests
                    if (results.length === result.entries.length) {
                        // done with everything, results are in results
                        // process final results here
                    }
                });
            }
        }
    });
});
Run Code Online (Sandbox Code Playgroud)