我有一个函数foo
,它在循环中进行多个(并行)异步调用。我需要等到所有呼叫的结果都可用。如何从foo
返回完整结果,或者在所有数据可用后触发某些处理?
我尝试将每个结果添加到数组中,但之后数组不会填充,直到我需要使用它之后。
function foo() {
var results = [];
for (var i = 0; i < 10; i++) {
someAsyncFunction({someParam:i}, function callback(data) {
results.push(data);
});
}
return results;
}
var result = foo(); // It always ends up being an empty array at this point.
注意:这个问题是按照现有通用"How do I return the response from an asynchronous call?" question的方式故意通用的。这个问题有一些很好的答案,但并不涵盖多个异步调用。还有一些其他问题提到多个调用,但我找不到任何基于循环的调用,有些只有jQuery答案等等。我希望这里有一些不通用的技术。取决于特定的图书馆。
答案 0 :(得分:11)
使用承诺。确切地说,Promise.all
就是为此设计的。
它接受一个数组(或可迭代的)promises,并返回一个新的promise,当数组的所有promises都已解析时,它会被解析。否则,它拒绝任何数组的承诺拒绝。
function someAsyncFunction(data, resolve, reject) {
setTimeout(function() {
if(Math.random() < .05) {
// Suppose something failed
reject('Error while processing ' + data.someParam);
} else {
// Suppose the current async work completed succesfully
resolve(data.someParam);
}
}, Math.random() * 1000);
}
function foo() {
// Create an array of promises
var promises = [];
for (var i = 0; i < 10; i++) {
// Fill the array with promises which initiate some async work
promises.push(new Promise(function(resolve, reject) {
someAsyncFunction({someParam:i}, resolve, reject);
}));
}
// Return a Promise.all promise of the array
return Promise.all(promises);
}
var result = foo().then(function(results) {
console.log('All async calls completed successfully:');
console.log(' --> ', JSON.stringify(results));
}, function(reason) {
console.log('Some async call failed:');
console.log(' --> ', reason);
});
&#13;
请注意,结果将根据承诺数组的顺序给出,而不是按照承诺被解决的顺序给出。
答案 1 :(得分:2)
一种简单的方法是在所有响应都在数组中时触发回调:
function foo(cb) {
var results = [];
for (var i = 0; i < 10; i++) {
someAsyncFunction({someParam:i}, function callback(data) {
results.push(data);
if(results.length===10){
cb(results);
}
});
}
}
foo(function(resultArr){
// do whatever with array of results
});
与Promise.all
方法的区别仅在于无法保证结果的顺序;但只需添加一些内容即可轻松实现。
答案 2 :(得分:2)
很久以前我在这里回答了一个非常相似的问题:Coordinating parallel execution in node.js。
然而,时代已经过去了。从那时起,一个非常好的图书馆已经出现,并且承诺设计模式已经被充分探索,甚至标准化为语言。如果您想了解如何使用原始代码完成,请单击上面的链接。如果你只想编写阅读...
async.js library基本上已经实现了上面链接中的代码。使用async,您编写的代码将如下所示:
var listOfAsyncFunctions = [];
for (var i = 0; i < 10; i++) {
(function(n){
// Construct an array of async functions with the expected
// function signature (one argument that is the callback).
listOfAsyncFunctions.push(function(callback){
// Note: async expects the first argument to callback to be an error
someAsyncFunction({someParam:n}, function (data) {
callback(null,data);
});
})
})(i); // IIFE to break the closure
}
// Note that at this point you haven't called the async functions.
// Pass the array to async.js and let it call them.
async.parallel(listOfAsyncFunctions,function (err,result) {
console.log(result); // result will be the same order as listOfAsyncFunctions
});
然而,async.js的作者已经做了更多。 Async还具有类似功能的数组操作:each,map,filter,reduce。它使得异步处理数组变得简单,并使代码更容易理解:
var listOfParams = [];
for (var i = 0; i < 10; i++) {
// Construct an array of params:
listOfParams.push({someParam:i});
}
async.map(listOfParams,someAsyncFunction,function (err,result) {
console.log(result);
});
async给你的另一个东西是如何处理异步任务的不同算法。比如说你想抓一个网站,但不希望他们禁止你的IP地址垃圾邮件他们的服务器。您可以使用async.series()
代替parallel
来一次处理一项任务:
// Set-up listOfAsyncFunctions as above
async.series(listOfAsyncFunctions,function (err,result) {
console.log(result); // result will be the same order as listOfAsyncFunctions
});
或者,如果您想一次处理3个任务:
async. parallelLimit(listOfAsyncFunctions, 3, function (err,result) {
console.log(result); // result will be the same order as listOfAsyncFunctions
});
Promise.all()
方法的工作方式与async.parallel()
类似,只是它适用于promises。您构造一个promises数组,然后将它们传递给Promise.all()
:
var listOfPromises = [];
for (var i = 0; i < 10; i++) {
// Construct an array of promises
listOfPromises.push(somePromiseFunction({someParam:i}));
}
Promise.all(listOfPromises).then(function(result){
console.log(result);
});
答案 3 :(得分:0)
不要使用Promise.all!如果您的任何一项承诺都失败了,那将使整个操作失败!
除非您对此前景感到满意,否则最好这样做:
function sleep(ms) {
return new Promise((resolve, reject) => {
console.log(`starting ${ms}`);
setTimeout(() => {
if (ms > 1000) {
console.log(`Threw out ${ms} because it took too long!`);
reject(ms);
} else {
console.log(`done ${ms}`);
resolve(ms);
}
}, ms);
});
}
(async () => {
console.log('aPromise, bPromise, cPromise executed concurrently as promises are in an array');
const start = new Date();
const aPromise = sleep(2000);
const bPromise = sleep(500);
const cPromise = sleep(5);
try {
const [a, b, c] = [await aPromise, await bPromise, await cPromise];
// The code below this line will only run when all 3 promises are fulfilled:
console.log(`slept well - got ${a} ${b} ${c} in ${new Date()-start}ms`);
} catch (err) {
console.log(`slept rough in ${err}ms`);
}
})();
答案 4 :(得分:0)
正如其他答案提到的那样,承诺是一个很好的选择。 Promise.all()
一并提到,但如果其中一项承诺失败,则返回并拒绝immediately
。
Promise.allSettled()仅在所有诺言完成后才返回,则是一个不错的选择。这样可以处理是否兑现了一些承诺而其他承诺被拒绝的情况。
以下是Mozilla文档中的示例:
Promise.allSettled([
Promise.resolve(33),
new Promise(resolve => setTimeout(() => resolve(66), 0)),
99,
Promise.reject(new Error('an error'))
])
.then(values => console.log(values));
// [
// {status: "fulfilled", value: 33},
// {status: "fulfilled", value: 66},
// {status: "fulfilled", value: 99},
// {status: "rejected", reason: Error: an error}
// ]