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);
async
await
版本
由于async
和await
关键字,这里的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()
)。
我会把它留给你设计。如果您在使用它时遇到问题,可以回来提出一个新问题。
如果它是使用 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);
async
await
版本
由于async
和await
关键字,这里的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()
)。
我会把它留给你设计。如果您在使用它时遇到问题,可以回来提出一个新问题。