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

nop*_*ole 0 asynchronous promise async-await bluebird es6-promise

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

// 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.");
Run Code Online (Sandbox Code Playgroud)

如果使用 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.");
})();
Run Code Online (Sandbox Code Playgroud)

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

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

tri*_*cot 6

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

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

正因为如此,请求确实会几乎同时触发,并且“完成”将同步(立即)输出。服务器可能会抱怨它在很短的时间内收到的许多请求。服务器通常对每个时间单位的请求数量设置最大限制,或者(在最坏的情况下)它们可能会在负载下下降。

还:

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

  • 返回的承诺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."));
Run Code Online (Sandbox Code Playgroud)

异步“递归”

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

(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);
Run Code Online (Sandbox Code Playgroud)

async await版本

由于asyncandawait关键字,for这里的循环不会同步进行所有迭代,而是仅在上一次迭代中创建的 promise 已解决时才进入下一次迭代。当涉及到一个接一个地做事情时,第二个代码片段比第一个更好。同样,它误解了fetchPromise 解析为的值。它解析为响应对象,而不是数据。您还应该声明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.");
})();
Run Code Online (Sandbox Code Playgroud)

并行运行

尽管 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."));
Run Code Online (Sandbox Code Playgroud)

限制并行性

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

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