版本

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

资源

更改语言