no-await-in-loop
Запрещает await внутри циклов
Выполнение операции для каждого элемента итерируемого объекта — распространённая задача. Однако выполнение await в рамках каждой операции может указывать на то, что программа не использует в полной мере преимущества параллельного выполнения, обеспечиваемые async/await.
Часто код можно переработать так, чтобы сначала создать все промисы, а затем получить доступ к результатам с помощью Promise.all() (или одного из других методов параллельной обработки промисов). В противном случае каждая следующая операция не начнётся, пока не завершится предыдущая.
Например, следующую функцию можно переработать, как показано ниже:
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;
}
Это также может быть полезно с точки зрения обработки ошибок, не всегда очевидной на первый взгляд. Если дан массив промисов, которые могут быть отклонены, последовательное ожидание ставит программу под угрозу необработанных отклонений промисов. Точное поведение при необработанных отклонениях зависит от среды выполнения вашего кода, но в любом случае они обычно считаются нежелательными. Например, в 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 в телах циклов.
Примеры корректного кода для этого правила:
/*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;
}
Примеры некорректного кода для этого правила:
/*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 bar(things) {
for (const thing of things) {
}
}
Параметры
У этого правила нет параметров.
Когда не следует использовать это правило
Во многих случаях итерации цикла на самом деле не независимы друг от друга, и ожидание внутри цикла уместно. Вот несколько примеров:
-
Любой код, который должен выполняться последовательно, например реализация обратного отсчёта или последовательная обработка элементов (когда каждый элемент уже обрабатывается параллельно), как в этом примере:
async function printCountdown() { for (let i = 0; i < 10; i++) { await new Promise(resolve => setTimeout(resolve, 1000)); // sleep 1 second console.log(i); } } -
Использование стандартных API браузера/ОС, которые по своей природе работают последовательно (это определяется базовой операционной системой), например
Fileили чтение содержимого каталогов, как в этом примере:async function writeNumbersToFile() { for (let i = 0; i < 100; i++) { // Doing this in parallel would interleave (garble) the resulting file contents. await fileWriteStream.write(i + "\n"); } } -
Любой код, в котором создание Promise выделяет ограниченный ресурс (оперативную память, файловые дескрипторы, пропускную способность сети), как в этом примере:
async function streamingProcess() { for (let i = 0; i < 10; i++) { await ramIntensiveAction(data[i]); } }async function checkFiles() { for (let i = 0; i < 10000; i++) { // Here some concurrency may be desirable to reduce I/O bottlenecks, // but not unbounded concurrency as that will fail, // running out of file descriptors. await openFileAndThrowIfContentsMeetCondition(filenames[i]); } } -
Результат одной итерации может использоваться как входные данные для другой.
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.
Ресурсы
© OpenJS Foundation and other contributors
Licensed under the MIT License.
https://eslint.org/docs/latest/rules/no-await-in-loop