no-await-in-loop

禁止 await 进入循环

对可迭代的每个元素执行操作是一项常见任务。但是,作为每个操作的一部分执行 await 可能表明程序没有充分利用 async/await 的并行化优势。

¥Performing an operation on each element of an iterable is a common task. However, performing an await as part of each operation may indicate that the program is not taking full advantage of the parallelization benefits of async/await.

通常,可以重构代码以一次创建所有 promise,然后使用 Promise.all()(或其他 promise 并发方法 之一)访问结果。否则,每个后续操作将在前一个操作完成之前不会开始。

¥Often, the code can be refactored to create all the promises at once, then get access to the results using Promise.all() (or one of the other promise concurrency methods). Otherwise, each successive operation will not start until the previous one has completed.

具体来说,可以重构以下函数,如下所示:

¥Concretely, the following function could be refactored as shown:

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 中,除非另有配置,否则为 未处理的拒绝会导致程序终止

¥This can be beneficial for subtle error-handling reasons as well. Given an array of promises that might reject, sequential awaiting puts the program at risk of unhandled promise rejections. The exact behavior of unhandled rejections depends on the environment running your code, but they are generally considered harmful regardless. In Node.js, for example, unhandled rejections cause a program to terminate unless configured otherwise.

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);
    }
}

规则详情

¥Rule Details

该规则不允许在循环体中使用 await

¥This rule disallows the use of await within loop bodies.

示例

¥Examples

此规则的正确代码示例:

¥Examples of correct code for this rule:

在线运行
/*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;
}

此规则的错误代码示例:

¥Examples of incorrect code for this rule:

在线运行
/*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;
}

何时不使用

¥When Not To Use It

在许多情况下,循环的迭代实际上并不相互独立,并且在循环中等待是正确的。举几个例子:

¥In many cases the iterations of a loop are not actually independent of each other, and awaiting in the loop is correct. As a few examples:

  • 一次迭代的输出可能用作另一次迭代的输入。

    ¥The output of one iteration might be used as the input to another.

    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;
            }
        }
    }
    
  • 循环可用于重试未成功的异步操作。

    ¥Loops may be used to retry asynchronous operations that were unsuccessful.

    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!';
    }
    
  • 循环可用于防止代码并行发送过多的请求。

    ¥Loops may be used to prevent your code from sending an excessive amount of requests in parallel.

    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 禁用注释禁用规则。

¥In such cases it makes sense to use await within a loop and it is recommended to disable the rule via a standard ESLint disable comment.

版本

此规则是在 ESLint v3.12.0 中引入。

资源

ESLint 中文网
粤ICP备13048890号