no-await-in-loop
禁止在循环内使用 await
对可迭代对象的每个元素执行操作是一项常见任务。但是,作为每个操作的一部分执行 await
可能表明程序没有充分利用 async
/await
的并行化优势。
通常,可以重构代码以一次创建所有 Promise,然后使用 Promise.all()
(或其他 Promise 并发方法 之一)访问结果。否则,每个后续操作将不会开始,直到前一个操作完成。
具体来说,以下函数可以按如下方式重构
async function foo(things) {
const results = [];
for (const thing of things) {
// Bad: each loop iteration is delayed until the entire asynchronous operation completes
results.push(await doAsyncWork(thing));
}
return results;
}
async function foo(things) {
const promises = [];
for (const thing of things) {
// Good: all asynchronous operations are immediately started.
promises.push(doAsyncWork(thing));
}
// Now that all the asynchronous operations are running, here we wait until they all complete.
const results = await Promise.all(promises);
return results;
}
这对于细微的错误处理原因也可能是有益的。如果给定一个可能拒绝的 Promise 数组,顺序等待会使程序面临未处理的 Promise 拒绝的风险。未处理的拒绝的确切行为取决于运行代码的环境,但无论如何它们通常被认为是有害的。例如,在 Node.js 中,未处理的拒绝会导致程序终止,除非另有配置。
async function foo() {
const arrayOfPromises = somethingThatCreatesAnArrayOfPromises();
for (const promise of arrayOfPromises) {
// Bad: if any of the promises reject, an exception is thrown, and
// subsequent loop iterations will not run. Therefore, rejections later
// in the array will become unhandled rejections that cannot be caught
// by a caller.
const value = await promise;
console.log(value);
}
}
async function foo() {
const arrayOfPromises = somethingThatCreatesAnArrayOfPromises();
// Good: Any rejections will cause a single exception to be thrown here,
// which may be caught and handled by the caller.
const arrayOfValues = await Promise.all(arrayOfPromises);
for (const value of arrayOfValues) {
console.log(value);
}
}
规则详情
此规则禁止在循环体中使用 await
。
示例
此规则的正确代码示例
在 Playground 中打开
/*eslint no-await-in-loop: "error"*/
async function foo(things) {
const promises = [];
for (const thing of things) {
// Good: all asynchronous operations are immediately started.
promises.push(doAsyncWork(thing));
}
// Now that all the asynchronous operations are running, here we wait until they all complete.
const results = await Promise.all(promises);
return results;
}
此规则的错误代码示例
在 Playground 中打开
/*eslint no-await-in-loop: "error"*/
async function foo(things) {
const results = [];
for (const thing of things) {
// Bad: each loop iteration is delayed until the entire asynchronous operation completes
results.push();
}
return results;
}
何时不使用
在许多情况下,循环的迭代实际上不是彼此独立的,并且在循环中等待是正确的。以下是一些示例:
-
一次迭代的输出可以用作另一次迭代的输入。
async function loopIterationsDependOnEachOther() { let previousResult = null; for (let i = 0; i < 10; i++) { const result = await doSomething(i, previousResult); if (someCondition(result, previousResult)) { break; } else { previousResult = result; } } }
-
循环可用于重试不成功的异步操作。
async function retryUpTo10Times() { for (let i = 0; i < 10; i++) { const wasSuccessful = await tryToDoSomething(); if (wasSuccessful) return 'succeeded!'; // wait to try again. await new Promise(resolve => setTimeout(resolve, 1000)); } return 'failed!'; }
-
循环可用于防止您的代码并行发送过多的请求。
async function makeUpdatesToRateLimitedApi(thingsToUpdate) { // we'll exceed our rate limit if we make all the network calls in parallel. for (const thing of thingsToUpdate) { await updateThingWithRateLimitedApi(thing); } }
在这种情况下,在循环内使用 await
是有意义的,建议通过标准的 ESLint 禁用注释禁用该规则。
版本
此规则在 ESLint v3.12.0 中引入。