如何等待一组异步回调函数?

我的代码在 javascript 中是这样的:

forloop {
//async call, returns an array to its callback
}

在完成所有这些异步调用之后,我想计算所有数组的最小值。

我怎么能等他们所有人呢?

我现在唯一的想法是使用一个名为 done 的布尔值数组,并在第 i 个回调函数中将 done [ i ]设置为 true,然后输入 while (并非所有都已完成){}

Edit: 我认为一个可能的,但是难看的解决方案是在每个回调中编辑 done 数组,然后在每个回调中设置了所有其他 done 的情况下调用一个方法,因此最后一个要完成的回调将调用 Continousmethod。

先谢谢你。

111804 次浏览

You can use jQuery's Deferred object along with the when method.

deferredArray = [];
forloop {
deferred = new $.Deferred();
ajaxCall(function() {
deferred.resolve();
}
deferredArray.push(deferred);
}


$.when(deferredArray, function() {
//this code is called after all the ajax calls are done
});

You haven't been very specific with your code, so I'll make up a scenario. Let's say you have 10 ajax calls and you want to accumulate the results from those 10 ajax calls and then when they have all completed you want to do something. You can do it like this by accumulating the data in an array and keeping track of when the last one has finished:

Manual Counter

var ajaxCallsRemaining = 10;
var returnedData = [];


for (var i = 0; i < 10; i++) {
doAjax(whatever, function(response) {
// success handler from the ajax call


// save response
returnedData.push(response);


// see if we're done with the last ajax call
--ajaxCallsRemaining;
if (ajaxCallsRemaining <= 0) {
// all data is here now
// look through the returnedData and do whatever processing
// you want on it right here
}
});
}

Note: error handling is important here (not shown because it's specific to how you're making your ajax calls). You will want to think about how you're going to handle the case when one ajax call never completes, either with an error or gets stuck for a long time or times out after a long time.


jQuery Promises

Adding to my answer in 2014. These days, promises are often used to solve this type of problem since jQuery's $.ajax() already returns a promise and $.when() will let you know when a group of promises are all resolved and will collect the return results for you:

var promises = [];
for (var i = 0; i < 10; i++) {
promises.push($.ajax(...));
}
$.when.apply($, promises).then(function() {
// returned data is in arguments[0][0], arguments[1][0], ... arguments[9][0]
// you can process it here
}, function() {
// error occurred
});

ES6 Standard Promises

As specified in kba's answer: if you have an environment with native promises built-in (modern browser or node.js or using babeljs transpile or using a promise polyfill), then you can use ES6-specified promises. See this table for browser support. Promises are supported in pretty much all current browsers, except IE.

If doAjax() returns a promise, then you can do this:

var promises = [];
for (var i = 0; i < 10; i++) {
promises.push(doAjax(...));
}
Promise.all(promises).then(function() {
// returned data is in arguments[0], arguments[1], ... arguments[n]
// you can process it here
}, function(err) {
// error occurred
});

If you need to make a non-promise async operation into one that returns a promise, you can "promisify" it like this:

function doAjax(...) {
return new Promise(function(resolve, reject) {
someAsyncOperation(..., function(err, result) {
if (err) return reject(err);
resolve(result);
});
});
}

And, then use the pattern above:

var promises = [];
for (var i = 0; i < 10; i++) {
promises.push(doAjax(...));
}
Promise.all(promises).then(function() {
// returned data is in arguments[0], arguments[1], ... arguments[n]
// you can process it here
}, function(err) {
// error occurred
});

Bluebird Promises

If you use a more feature rich library such as the Bluebird promise library, then it has some additional functions built in to make this easier:

 var doAjax = Promise.promisify(someAsync);
var someData = [...]
Promise.map(someData, doAjax).then(function(results) {
// all ajax results here
}, function(err) {
// some error here
});

You can emulate it like this:

  countDownLatch = {
count: 0,
check: function() {
this.count--;
if (this.count == 0) this.calculate();
},
calculate: function() {...}
};

then each async call does this:

countDownLatch.count++;

while in each asynch call back at the end of the method you add this line:

countDownLatch.check();

In other words, you emulate a count-down-latch functionality.

Use an control flow library like after

after.map(array, function (value, done) {
// do something async
setTimeout(function () {
// do something with the value
done(null, value * 2)
}, 10)
}, function (err, mappedArray) {
// all done, continue here
console.log(mappedArray)
})

Checking in from 2015: We now have native promises in most recent browser (Edge 12, Firefox 40, Chrome 43, Safari 8, Opera 32 and Android browser 4.4.4 and iOS Safari 8.4, but not Internet Explorer, Opera Mini and older versions of Android).

If we want to perform 10 async actions and get notified when they've all finished, we can use the native Promise.all, without any external libraries:

function asyncAction(i) {
return new Promise(function(resolve, reject) {
var result = calculateResult();
if (result.hasError()) {
return reject(result.error);
}
return resolve(result);
});
}


var promises = [];
for (var i=0; i < 10; i++) {
promises.push(asyncAction(i));
}


Promise.all(promises).then(function AcceptHandler(results) {
handleResults(results),
}, function ErrorHandler(error) {
handleError(error);
});

This is the most neat way in my opinion.

Promise.all

FetchAPI

(for some reason Array.map doesn't work inside .then functions for me. But you can use a .forEach and [].concat() or something similar)

Promise.all([
fetch('/user/4'),
fetch('/user/5'),
fetch('/user/6'),
fetch('/user/7'),
fetch('/user/8')
]).then(responses => {
return responses.map(response => {response.json()})
}).then((values) => {
console.log(values);
})

In Node.js you can use async/await to control the async flow

  • async/await is supported in Node.js 7.6
  • util function to promisify callback is supported in Node.js v8

Sample Code:

const foo = async () => {
try {
const ids = [100, 101, 102];
const fetchFromExternalApi = util.promisify(fetchFromExternalApiCallback);
const promises = ids.map((id) => fetchFromExternalApi(id));
const dataList = await Promise.resolve(promises); // dataList is an array
return dataList;
} catch (err) {
// error handling
}
};