在下面的代码中,我试图一次性发出多个(大约10个)HTTP请求和RSS解析。
我在我需要访问的URI数组上使用标准forEach
构造并解析结果。
代码:
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
据我所知,在调用函数时,我应该使用回调函数。但是,在这个例子中,我能想到使用回调的唯一方法是调用一个函数,该函数计算它被调用的次数,并且仅在被调用与feedsToFetch.length
相同的次数时才继续。好像很讨厌。
所以我的问题是,在node.js 中处理此类情况的最佳方法是什么。
最好没有任何形式的阻挡! (我仍然希望那种超快的速度)。这是承诺还是其他什么?
谢谢, 丹尼
答案 0 :(得分:10)
没有必要的黑客
我建议使用async模块,因为它使这些事情变得更加容易。
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!");
});
或者,您可以构建一个异步操作数组并将它们传递给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 !");
});
或者你可以自己动手
也许你感到更加野心勃勃,或者你可能不想依赖async
依赖。也许你只是像我一样无聊。无论如何,我故意复制了async.eachSeries
的API,以便于理解其工作原理。
我们删除了这里的评论后,我们只有 9行代码</ strong>,可以重复用于我们想要异步处理的任何数组!它不会修改原始数组,可以将错误发送到“短路”迭代,并且可以使用单独的回调。它也适用于空数组。只有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();
}
现在让我们创建一个样本异步函数来使用它。我们会在这里用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);
}
好的,让我们看看它们是如何工作的!
tasks = ["cat", "hat", "wat"];
asyncForEach(tasks, function(uri, done) {
sampleAsync(uri, function(message) {
console.log(message);
done();
});
}, function() {
console.log("done");
});
输出(每次输出前延迟500 ms)
async cat !
async hat !
async wat !
done
答案 1 :(得分:9)
无解决方案
Promises to be included in next JavaScript version
流行的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)...
}
另请注意,您不需要在此处使用闭包来累积结果。
Bluebird API Docs也写得很好,有很多例子,所以它更容易上手。
一旦我学会了Promise模式,它就让生活变得如此简单。我不能推荐它。
另外,这里有a great article关于使用promises处理异步函数的不同方法,async
模块和其他
希望这有帮助!
答案 2 :(得分:1)
使用url列表的副本作为队列来跟踪到达时间变得简单: (所有更改都已评论)
var q=feedsToFetch.slice(); // dupe to censor upon url arrival (to track progress)
feedsToFetch.forEach(function (feedUri)
{
feed(feedUri, function(err, feedArticles)
{
if (err)
{
throw err;
}
else
{
articles = articles.concat(feedArticles);
}
q.splice(q.indexOf(feedUri),1); //remove this url from list
if(!q.length) done(); // if all urls have been removed, fire needy code
});
});
function done(){
// Code I want to run once all feedUris have been visited
}
最后,这并不是那么多&#34;更脏&#34;比承诺,并提供机会重新加载未完成的网址(一个单独的计数器不会告诉你哪一个失败)。对于这个简单的并行下载任务,它实际上会为你的项目实现Promises而不是一个简单的队列添加更多的代码,并且Promise.all()不是最容易发现的最直观的地方。一旦你进入子查询,或想要比火车残骸更好的错误处理,我强烈建议使用Promises,但你不需要火箭发射器来杀死一只松鼠......