如何等待异步回调函数集

How can I wait for set of asynchronous callback functions?

本文关键字:异步 回调 函数 等待 何等待      更新时间:2023-09-26

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

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

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

我怎么能等他们呢?

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

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

你的代码不是很具体,所以我会编造一个场景。 假设您有 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 Promise

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

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 的环境(现代浏览器或节点.js或使用 babeljs 转译或使用 promise polyfill(,那么您可以使用 ES6 指定的承诺。 有关浏览器支持,请参阅此表。 几乎所有当前浏览器都支持承诺,除了IE。

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

从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 个异步操作并在它们全部完成时收到通知,我们可以使用 本机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);
});

你可以将 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
});

你可以像这样模拟它:

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

然后每个异步调用都这样做:

countDownLatch.count++;

在方法末尾的每个异步回调中,您添加以下行:

countDownLatch.check();

换句话说,您可以模拟倒计时闩锁功能。

在我看来

,这是最整洁的方式。

承诺.全部

获取接口

(由于某种原因,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);
})

使用控制流库,如 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)
})

在 Node 中.js您可以使用 async/await 来控制异步流

  • 节点支持 async/await.js 7.6
  • 在 Node.js v8 中支持用于承诺回调的 util 函数

示例代码:

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

>我看到几个带有Promise.all()的响应,但如果任何承诺生成异常,此函数将停止...

2022 年的最佳解决方案是 Promise.allSettled()(此处的文档:https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Promise/allSettled

快速示例:

const allPromises = [];
for (r in records) {
  const promise = update_async(r);
  allPromises.push(promise);
};
await Promise.allSettled(allPromises);

最后,你用allPromises得到一个数组,其中包含每个承诺的结果:

  • 当确定 --> {status: “fulfilled”, value: xxx }
  • 当错误 --> {status: "rejected", reason: Error: an error}