Node.js:执行多个异步操作的最佳方式,然后执行其他操作?

dan*_*own 8 javascript node.js promise

在下面的代码中,我试图一次性发出多个(大约10个)HTTP请求和RSS解析.

我在forEach我需要访问和解析结果的URI数组上使用标准构造.

码:

var articles;

feedsToFetch.forEach(function (feedUri)
{   
        feed(feedUri, function(err, feedArticles) 
        {
            if (err)
            {
                throw err;
            }
            else
            {
                articles = articles.concat(feedArticles);
            }
        });
 });

 // Code I want to run once all feedUris have been visited
Run Code Online (Sandbox Code Playgroud)

我明白,在调用函数时,我应该使用回调.但是,我能想到在这个例子中使用回调的唯一方法是调用一个函数来计算它被调用了多少次,并且只有当它被调用的次数相同时才会继续调用feedsToFetch.lengthhacky.

所以我的问题是,在node.js中处理这种情况的最佳方法什么.

优选地,没有任何形式的阻塞!(我仍然希望那种超快的速度).这是承诺还是其他什么?

谢谢,丹尼

Tha*_*you 10

没有必要的黑客

我建议使用异步模块,因为它使这些事情变得容易多了.

async提供async.eachSeries作为异步替换,arr.forEach并允许您在完成后传递done回调函数.它将处理一系列中的每个项目,就像一样forEach.此外,它会方便地将错误冒充到您的回调中,这样您就不必在循环中包含处理程序逻辑.如果需要/需要并行处理,可以使用async.each.

会有不堵之间async.eachSeries调用和回调.

async.eachSeries(feedsToFetch, function(feedUri, done) {

  // call your async function
  feed(feedUri, function(err, feedArticles) {

    // if there's an error, "bubble" it to the callback
    if (err) return done(err);

    // your operation here;
    articles = articles.concat(feedArticles);

    // this task is done
    done();
  });
}, function(err) {

  // errors generated in the loop above will be accessible here
  if (err) throw err;

  // we're all done!
  console.log("all done!");
});
Run Code Online (Sandbox Code Playgroud)

或者,您可以构建一个异步操作数组并将它们传递给async.series.Series将以一系列(非并行)处理结果,并在每个函数完成时调用回调.使用它的唯一原因async.eachSeries是如果您更喜欢熟悉的arr.forEach语法.

// create an array of async tasks
var tasks = [];

feedsToFetch.forEach(function (feedUri) {

  // add each task to the task array
  tasks.push(function() {

    // your operations
    feed(feedUri, function(err, feedArticles) {
      if (err) throw err;
      articles = articles.concat(feedArticles);
    });
  });
});

// call async.series with the task array and callback
async.series(tasks, function() {
 console.log("done !");
});
Run Code Online (Sandbox Code Playgroud)

或者你可以自己滚动

也许你感觉更加雄心勃勃,或者你可能不想依赖async依赖.也许你只是像我一样无聊.无论如何,我故意复制API,async.eachSeries以便于理解其工作原理.

一旦我们删除了这里的注释,我们只有9行代码可以重复用于我们想要异步处理的任何数组!它不会修改原始数组,可以将错误发送到"短路"迭代,并且可以使用单独的回调.它也适用于空数组.相当多的功能只有9行:)

// void asyncForEach(Array arr, Function iterator, Function callback)
//   * iterator(item, done) - done can be called with an err to shortcut to callback
//   * callback(done)       - done recieves error if an iterator sent one
function asyncForEach(arr, iterator, callback) {

  // create a cloned queue of arr
  var queue = arr.slice(0);

  // create a recursive iterator
  function next(err) {

    // if there's an error, bubble to callback
    if (err) return callback(err);

    // if the queue is empty, call the callback with no error
    if (queue.length === 0) return callback(null);

    // call the callback with our task
    // we pass `next` here so the task can let us know when to move on to the next task
    iterator(queue.shift(), next);
  }

  // start the loop;
  next();
}
Run Code Online (Sandbox Code Playgroud)

现在让我们创建一个样本异步函数来与它一起使用.我们假设延迟时间setTimeout为500毫秒.

// void sampleAsync(String uri, Function done)
//   * done receives message string after 500 ms
function sampleAsync(uri, done) {

  // fake delay of 500 ms
  setTimeout(function() {

    // our operation
    // <= "foo"
    // => "async foo !"
    var message = ["async", uri, "!"].join(" ");

    // call done with our result
    done(message);
  }, 500);
}
Run Code Online (Sandbox Code Playgroud)

好的,让我们看看它们是如何工作的!

tasks = ["cat", "hat", "wat"];

asyncForEach(tasks, function(uri, done) {
  sampleAsync(uri, function(message) {
    console.log(message);
    done();
  });
}, function() {
  console.log("done");
});
Run Code Online (Sandbox Code Playgroud)

输出(每个输出前延迟500 ms)

async cat !
async hat !
async wat !
done
Run Code Online (Sandbox Code Playgroud)


aar*_*sil 10

无需解决方案

承诺包含在下一个JavaScript版本中

流行的Promise库为您提供了一个.all()确切用例的方法(等待一堆异步调用完成,然后再执行其他操作).这是您的场景的完美搭配

Bluebird也有.map(),它可以获取一系列值并使用它来启动Promise链.

以下是使用Bluebird的示例.map():

var Promise = require('bluebird');
var request = Promise.promisifyAll(require('request'));

function processAllFeeds(feedsToFetch) {    
    return Promise.map(feedsToFetch, function(feed){ 
        // I renamed your 'feed' fn to 'processFeed'
        return processFeed(feed) 
    })
    .then(function(articles){
        // 'articles' is now an array w/ results of all 'processFeed' calls
        // do something with all the results...
    })
    .catch(function(e){
        // feed server was down, etc
    })
}

function processFeed(feed) { 
    // use the promisified version of 'get'
    return request.getAsync(feed.url)... 
}
Run Code Online (Sandbox Code Playgroud)

另请注意,您无需在此处使用闭包来累积结果.

蓝鸟API文档都写得很好过,有很多的例子,所以它可以更容易回升.

一旦我学会了Promise模式,它就让生活变得如此简单.我不能推荐它.

另外,这里有一篇很棒的文章,讲述了使用promises,async模块和其他方法处理异步函数的不同方法

希望这可以帮助!

  • @naomik我更新了答案以反映无黑客状态 (2认同)