promises和async/await如果是批处理会不会创建很多线程,是不是比同步版本好?

Does promises and async / await create a lot of threads if it is batch processing and is it better than the synchronous version?

如果它是使用 Node.js 编写的 JavaScript 程序,它将查看所​​有员工,获取一些数据,进行一些计算,然后 post 将其返回到另一台服务器:

// without error handling to keep it simple for now

for (let employee of employees) {
  new Promise(function(resolve) {
    fetch(someUrlToServerA + employee.id).then(resolve);
  }.then((data) => {
    let result = doCalculations(data);
    return postData(someUrlToServerB + employee.id, result).then(resolve);
  }.then(() => console.log("Finished for", employee.id));
}
console.log("All done.");

如果使用 async/await 编写,它可能大致相当于:

(async function(){
  for (let employee of employees) {
    data = await fetch(someUrlToServerA + employee.id);

    let result = doCalculations(data);
    await postData(someUrlToServerB + employee.id, result);

    console.log("Finished for", employee.id);
  }
  console.log("All done.");
})();

假设有 6000 名员工,那么程序(运行 使用 Node.js)不会继续向 ServerA 发出请求,实际上几乎打印出 "All done"立即(可能在几秒钟内),但现在只有 6000 个线程都试图从 ServerA 获取数据,然后进行计算,然后 post 到 ServerB?有更好的方法吗?

并行发出请求似乎有一些好处: 如果对 ServerA 的每个请求都需要 3 秒,那么向它发出并行请求可能会节省一些时间,如果它可以在 3 秒内 return 4 个请求。但是如果同时向 ServerA 发送许多请求,那么它可能会阻塞请求并且一次只能处理几个请求。或者,使用这种方法,系统实际上是否通过限制同时连接的数量来限制同时获取的数量。那么假设它同时限制4个连接,那么"All done"打印很快,但内部同时处理4个员工,这样好吗?如果 ServerA 和 ServerB 不抱怨同时有多个请求,并且计算,假设需要毫秒才能完成,那么这种方法可能比同步版本需要 1/4 的时间来完成?

首先,JavaScript 通常用一个线程执行您的 JavaScript 代码,无论您是否使用 promises。当您使用 Web Workers 时,多个线程可以发挥作用,并且在 JavaScript 依赖的较低级别的非 JavaScript 代码中(如文件 I/O、HTTP 请求处理、.. .etc).

第一段代码设计的不好,因为for循环是同步执行的,所以下一次迭代不会等待上一次迭代的promise resolve。

因此,确实会几乎同时触发所有请求,"done"会同步(立即)输出。服务器可能会抱怨它在很短的时间内收到了很多请求。服务器通常对每个时间单位的请求数量设置最大限制,或者(在最坏的情况下)它们可能会在负载下崩溃。

还有:

  • 您正在使用 promise 构造函数反模式:当您已经有一个 promise(由 fetch 返回)时,不要创建 new Promise

  • fetch 返回的承诺没有直接解析为数据。相反,它解析为一个响应对象,该对象公开异步获取数据的方法。

这里有一种可能的方式来链接承诺,因此下一次提取只会在前一个有响应时发生:

let promise = Promise.resolve();
for (let employee of employees) {
    promise = promise.then(() => fetch(someUrlToServerA + employee.id))
        .then((response) => response.json()) // assuming you get data as JSON
        .then((data) => postData(someUrlToServerB + employee.id, doCalculations(data)))
        .then(() => console.log("Finished for", employee.id));
}
promise.then(() => console.log("All done."));

异步"recursion"

上述解决方案一次性创建了所有承诺。要将承诺的创建延迟到真正需要时,您可以创建一个异步循环:

(function loop(i) {
    if (i >= employees.length) {
        console.log("All done.");
        return;
    }
    let employee = employees[i];
    fetch(someUrlToServerA + employee.id))
        .then((response) => response.json()) // assuming you get data as JSON
        .then((data) => postData(someUrlToServerB + employee.id, doCalculations(data)))
        .then(() => console.log("Finished for", employee.id)
        .then(() => loop(i+1));
})(0);

asyncawait版本

由于asyncawait关键字,这里的for循环并没有同步进行所有迭代,而是只有在上一次创建的promise时才进入下一次迭代迭代已经解决。 在一个接一个地执行操作时,第二个代码片段比第一个版本更好。同样,它误解了 fetch promise 解析的值。它解析为响应对象,而不是数据。您还应该将 data 声明为变量,否则它将是全局变量(在草率模式下):

(async function(){
    for (let employee of employees) {
        let response = await fetch(someUrlToServerA + employee.id);
        let data = await response.json();
        let result = doCalculations(data);
        await postData(someUrlToServerB + employee.id, result);
        console.log("Finished for", employee.id);
    }
    console.log("All done.");
})();

运行并行

虽然JavaScript不能并行执行多行代码,但底层API(可能依赖非JS代码和操作系统调用)可以运行在平行下。因此,处理 HTTP 请求并通知 JavaScript(通过其事件队列)请求有响应的进程确实可以 运行 并行。

如果你想这样做,那么你应该同步启动一些(或所有)fetch 调用,并使用 Promise.all 等待所有返回的承诺解决。

您的第一段代码需要重写为:

let promises = [];
for (let employee of employees) {
    promises.push(fetch(someUrlToServerA + employee.id)
        .then((response) => response.json()) // assuming you get data as JSON
        .then((data) => postData(someUrlToServerB + employee.id, doCalculations(data))
        .then(() => console.log("Finished for", employee.id)))
}
Promise.all(promises).then(() => console.log("All done."));

限制并行度

如果你想要一个混合解决方案,如果未决承诺的数量被限制在,比方说,4,那么你需要结合使用 Promise.all(处理 4 个承诺的数组),使用第一个代码块中发生的链接(使用 promise = promise.then())。

我会把它留给你设计。如果您在使用它时遇到问题,可以回来提出一个新问题。