如果是批处理,承诺和异步/等待是否会创建很多线程,并且是否比同步版本更好?



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

// 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 获取数据,然后进行计算,并发布到 ServerB? 有没有更好的方法

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

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

第一段代码设计得不好,因为for循环是同步执行的,因此下一次迭代不会等待上一次迭代的承诺得到解决。

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

也:

  • 您正在使用 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."));

异步"递归">

上述解决方案可一次性创建所有承诺。若要延迟创建承诺,直到确实需要它们,您可以创建一个异步循环:

(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循环不会同步执行所有迭代,而只有在上一次迭代中创建的承诺已解析后才能进入下一次迭代。 第二个代码片段在接一个地做事时比第一个更好。同样,它误解了fetch承诺解析的值。它解析为响应对象,而不是数据。您还应该将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()(。

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

最新更新