版本

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(await doAsyncWork(thing));
  }
  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 中引入。

資源

變更語言