如何等待异步回调函数集?


95

我在javascript中看起来像这样的代码:

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

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

我要如何等待所有人?

我现在唯一的想法是拥有一个名为done的布尔数组,并在第i个回调函数中将done [i]设置为true,然后说while(不是全部完成){}

编辑:我想一个可能但很丑陋的解决方案是在每个回调中编辑完了的数组,然后如果每个回调中都设置了所有其他完成,则调用一个方法,因此要完成的最后一个回调将调用继续方法。

提前致谢。


1
在异步上,您是指等待Ajax请求完成吗?
Peter Aron Zentai '04年

6
请注意,while (not all are done) { }这是行不通的。当您忙于等待时,任何回调都无法运行。
cHao 2012年

是。我正在等待对外部API的异步调用返回,以便它将触发回调方法。是的,我意识到,这就是为什么我在这里寻求帮助的原因:D
codersarepeople 2012年

您可以尝试以下方法:github.com/caolan/async非常好的一组异步实用程序功能。
保罗·格雷森

Answers:


191

您对代码的了解不是很明确,所以我将提出一个方案。假设您有10个ajax调用,并且想要累积这10个ajax调用的结果,然后在它们全部完成后就想做些什么。您可以通过在数组中累积数据并跟踪最后一个数据的完成时间来做到这一点:

手动计数器

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
        }
    });
}

注意:错误处理在这里很重要(未显示,因为它特定于您进行ajax调用的方式)。您将要考虑如何处理一个ajax调用永远不会完成的情况,该错误可能是由于错误或长时间卡住,或者很长时间后超时。


jQuery的承诺

在2014年为我添加了答案。这些天来,promise通常用于解决此类问题,因为jQuery $.ajax()已经返回了promise,并且$.when()将在一组promise全部解决时通知您,并会为您收集返回结果:

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标准承诺

如kba的回答所指定:如果您拥有一个内置本机promise的环境(现代浏览器或node.js或使用babeljs transpile或诺言polyfill),则可以使用ES6指定的诺言。请参阅此表以获取浏览器支持。除了IE,几乎所有当前的浏览器都支持Promise。

如果doAjax()返回承诺,则可以执行以下操作:

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
});

如果您需要将非承诺异步操作变为返回承诺的异步操作,则可以像这样“承诺”它:

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

然后,使用上面的模式:

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 promise库),那么它将内置一些附加功能以使其更容易:

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

4
@kba-因为所有技术仍然适用,所以我不会完全把这个答案称为过时的,尤其是如果您已经使用jQuery for Ajax。但是,我已经通过几种方式对其进行了更新,以包含本机承诺。
jfriend00

过去的日子里,有一种更清洁的解决方案,甚至不需要jquery。我正在使用FetchAPI和Promises
philx_x

@philx_x-您如何处理IE和Safari支持?
jfriend00 '16

@ jfriend00 github做了一个polyfill github.com/github/fetch。或者我不确定babel是否支持fetch。babeljs.io
philx_x

@philx_x-这样想。您现在需要一个polyfill库才能使用访存。您对避免使用Ajax库的评论有些不屑一顾。提取很不错,但是距离无需使用polyfill就可以使用它还有很多年了。甚至还不是所有浏览器的最新版本。是的,它并没有真正改变我的答案。我有一个doAjax()返回诺言的选择之一。与相同fetch()
jfriend00 '16

17

从2015年起签入:我们现在在最近的浏览器(Edge 12,Firefox 40,Chrome 43,Safari 8,Opera 32和Android浏览器4.4.4和iOS Safari 8.4,但没有Internet Explorer,Opera Mini和旧版本)中都具有本机承诺 Android)。

如果我们要执行10个异步操作并在完成所有操作时得到通知,则可以使用native Promise.all,而无需任何外部库:

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);
});

2
Promises.all()应该是Promise.all()
jfriend00 2015年

1
您的答案还需要参考可以使用的浏览器Promise.all(),其中不包括当前版本的IE。
jfriend00

10

您可以将jQuery的Deferred对象与when方法一起使用。

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
});

7
这个问题没有标记jQuery,通常意味着OP不需要jQuery答案。
jfriend00'4

8
@ jfriend00当它已经在jQuery中创建时,我不想重新发明轮子
Paul

4
@Paul,所以宁愿重新发明轮子,包括40kb的垃圾来做简单的事情(递延)
Raynos 2012年

2
但是并不是每个人都可以或想要使用jQuery,SO上的习惯是通过是否用jQuery标记问题来表明这一点。
jfriend00'4

4
$ .when调用是此示例不正确。要等待一系列延迟/承诺,您需要使用$ .when.apply($,promises).then(function(){/ *做东西* /})。
danw

9

您可以像这样模拟它:

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

然后每个异步调用都会执行此操作:

countDownLatch.count++;

在每次异步调用中,在方法末尾添加以下行:

countDownLatch.check();

换句话说,您模拟了倒计时锁存功能。


在99%的用例中,Promise是可行的方法,但是我喜欢这个答案,因为它说明了一种在Promise polyfill比使用它的JS大的情况下管理异步代码的方法!
Sukima

6

我认为这是最整洁的方式。

无极

提取API

(由于某些原因,Array.map无法在.then函数内部运行。但是您可以使用.forEach和[] .concat()或类似的东西)

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);
})

1
我认为这应该是return responses.map(response => { return response.json(); })return responses.map(response => response.json())

1

使用类似的控制流库 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)
})
By using our site, you acknowledge that you have read and understand our Cookie Policy and Privacy Policy.
Licensed under cc by-sa 3.0 with attribution required.