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 中引入。