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