等待多个异步函数在 javascript 内完成

Waiting for multiple async functions to finish in javascript

我想向服务器发出一系列 ajax 请求,然后使用我之前收到的数据执行最终 ajax 请求。显然,我需要等待前面的请求完成才能执行最后一个请求。我在 javascript.

中实现时遇到问题

我不想让服务器不堪重负,所以理想情况下,所有请求都会按顺序完成。

我的简单测试代码如下(用sleep代替web请求):

const sleep = (delay) => new Promise((resolve) => setTimeout(resolve, delay));

var urls = ['1', '2', '3'];

const slowFunc = () => {

    urls.forEach(async (url) => {

        //Don't change this section!
        console.log("a"+url);
        await sleep(5000);
        console.log("b"+url); //I want this to run before c
    });

};

slowFunc();
console.log("c");

这在睡眠完成之前打印“c”,这是错误的。我怎样才能得到如下输出?

a1
b1
a2
b2
a3
b3
c

出于兴趣,我将如何获得此输出? (a 和 b 部分中的确切顺序并不重要。)

a1
a2
a3
b1
b2
b3
c

我尝试阅读 ES2018: asynchronous iteration 但它让我大吃一惊。

更新:我很快就重新考虑了我的例子,所以这是一个更好的例子(仍然不起作用):

var urls = ['https://cdnjs.cloudflare.com/ajax/libs/dompurify/2.2.0/purify.min.js', 'https://cdnjs.cloudflare.com/ajax/libs/systemjs/6.8.3/system.min.js', 'https://cdnjs.cloudflare.com/ajax/libs/slim-select/1.18.6/slimselect.min.js'];
var results = {};

const webRequest = (url) => {
    $.ajax({
        type: "GET",
        url: url,
    }).then(data => {
        results[url] = data;
        console.log("b"+url+","+results[url]); //I want this to run before c
    });
}

const slowFunc = () => {

    urls.forEach((url) => {
        console.log("a"+url);
        webRequest(url);
    });

};

slowFunc();
console.log("c");

感谢到目前为止的评论。

更新 2:Web 请求问题的解决方案,基于 Antonio Della Fortuna 的建议:

var urls = ['https://cdnjs.cloudflare.com/ajax/libs/dompurify/2.2.0/purify.min.js', 'https://cdnjs.cloudflare.com/ajax/libs/systemjs/6.8.3/system.min.js', 'https://cdnjs.cloudflare.com/ajax/libs/slim-select/1.18.6/slimselect.min.js'];
var results = {};

const webRequest = (url) => {
    return new Promise((resolve, reject) => {

        $.ajax({
            type: "GET",
            url: url,
            error: function (data, status, er) { 
                console.log("b,"+url+",failed");
                resolve(); 
            },
        }).then(data => {
            results[url] = data;
            console.log("b,"+url+","+results[url]); //I want this to run before c
            resolve();
        });
    });
}

const slowFunc = async () => {

    for (let i = 0; i < urls.length; i++)
    {
        var url = urls[i];
        console.log("a,"+url);
        await webRequest(url);
    };

};

slowFunc().then(() => {
    console.log("c");
    console.log(results);
})

在迭代时执行异步操作并不像您预期​​的那样有效。

当您执行 forEach 时,每个元素都将被同步迭代。因此,每个元素都将被迭代并调用回调函数,这就是为什么您首先看到每个元素的 'a' 日志。

例外情况是使用 for...of 循环,但对于其他迭代器,await 只会在回调函数内阻塞。

如果您试图将一段时间内的请求量限制为 API,您可以实施漏桶算法。或者,您可以使用延迟函数将迭代重构为 for...of 循环,以阻止保持顺序但不太理想的请求,因为请求的速度将是您的延迟时间加上完成其他异步任务的时间。

有两种方法取决于您的用例,您可以在此处找到工作示例 -> https://codesandbox.io/s/zen-carson-ksgzf?file=/src/index.js:569-624:

  1. 并行解决方案:您可以运行并行处理函数内部的请求,然后像这样打印"c"
    const sleep = (delay) => new Promise((resolve) => setTimeout(resolve, delay));

    var urls = ["1", "2", "3"];
    
    const slowFunc = async () => {
      await Promise.all(
        urls.map(async (url) => {
          //Don't change this section!
          console.log("a" + url);
          await sleep(5000);
          console.log("b" + url); //I want this to run before c
        })
      );
    };
    
    slowFunc().then(() => {
      console.log("c");
    });
  1. 同步解决方案:您可以运行所有请求就好像它们是同步的并按顺序等待:

    const slowFuncSeq = async () => {
      for (const url of urls) {
        //Don't change this section!
        console.log("a" + url);
        await sleep(5000);
        console.log("b" + url); //I want this to run before c
      }
    };

    slowFuncSeq().then(() => {
      console.log("c");
    })