Как использовать async/await в цикле forEach?
9 февраля 2021 г. • ☕️ 4 мин.
Проблема
Не смотря на то, что понимание этого вопроса кроется в понимании возврата из асинхронной функции, такой код может ввести в заблуждение начинающего разработчика.
const fs = require("fs/promises")
async function main() {
const dir = await fs.readdir("./")
const files = []
dir.forEach(async name => { const file = await fs.readFile(name)
files.push(file)
})
console.log(files) // []
}
main()
Как устроен array.forEach
?
forEach
является синхронной функций и не умеет ждать
завершения асинхронной работы в callback
.
Как устроена async
функция?
Мы передаем асинхронную (async
) функцию, как (callback
) в forEach
. Внутри этой функции, мы можем использовать ключевое слово await
. При вызове, асинхронная фунция возвращает Promise
, который надо подождать с помощью ключевого слова await
либо функции .then
.
Что же здесь происходит?
forEach
запускает несколько асинхронных функций, и тут же выводит пустой массив files
в консоль. Все асинхронные функции будут завершены когда-нибудь позже.
Получается, что callback
возвращает Promise
, который никто не ожидает.
Как добиться результата?
Последовательное выполнение
async function main() {
const dir = await fs.readdir("./")
const files = []
for (const name of dir) { const file = await fs.readFile(name)
files.push(file)
}
console.log(files)
}
При последовательном выполнении используется цикл for...of
. Он не требует передачи callback
и весь код исполняется в основном логическом потоке, поэтому await
будет работать, как и ожидается.
Классический цикл for
или while
будут тоже работать.
Одновременное (concurrent
) выполнение
async function main() {
const dir = await fs.readdir("./")
const files = await Promise.all(dir.map(async name => fs.readFile(name)))
console.log(files)
}
При одновременном выполнении используется комбинация из двух частей.
- функция
array.map
, которая возвращает массив изPromise
- функция
Promise.all
, которая позволяет подождать завершения массива изPromise
Этот вариант похож на первый с forEach
, где мы запускали несколько асинхронных операций одновременно, только здесь, мы явно ждем их завершения.
Заключение
Асинхронный код всегда требует того, чтобы мы дождались результата в явном виде. Можно легко упустить из виду асинхронную операцию, которая “отсоединится” от основного логического потока программы.
При проблемах в понимании асинхронного кода, всегда стоит задавать себе вопрос: Что я сделал для того, чтобы дождаться здесь результата?