
Привет и добро пожаловать! 👋
Сегодня мы постараемся осветить очень важный аспект работы с async/await внутри цикла, если вы не имеете ни малейшего представления о разнице, то это обязательно к прочтению, а если вы уже знали, это будет освежающим напоминание, так как это может вызвать серьезные проблемы при написании кода и заставит вас тратить время на отладку, так что давайте.
Что такое асинхронный/ожидающий?
Прежде чем мы углубимся в объяснение разницы, давайте сначала поговорим об async/await.
Async/await — мощная функция, упрощающая асинхронное программирование в JavaScript. Он обеспечивает более читаемый и синхронный синтаксис для работы с промисами и обработки асинхронных операций.
Ключевое слово async используется для объявления функции как асинхронной. Асинхронная функция неявно возвращает обещание, что позволяет нам использовать в нем await. Ключевое слово await можно применить к обещанию, приостанавливая выполнение функции до тех пор, пока обещание не будет разрешено или отклонено. Это помогает избежать ада обратных вызовов и написать код, который выполняется синхронно, даже если он имеет дело с асинхронными операциями.
async function getUserById(id) {
// Getting data
}
async function getUser(req, res) {
let result = await getUserById(req.params['id']);
// Continue after getting data
}
Использование async/await с циклом for
Цикл for — это традиционная конструкция цикла в JavaScript, используемая для перебора массива или других итерируемых объектов.
При использовании async/await с циклом for каждая итерация ожидает разрешения предыдущей асинхронной операции, прежде чем перейти к следующей итерации. Это сохранит операции цикла в нужном порядке и не преподнесет нам никаких сюрпризов.
Представьте, что у нас есть асинхронная функция с именем getData, которая бездействует в течение заданного времени, а затем регистрирует сообщение DONE с индексом, который использовался для ее вызова.
async function getData(i, time) {
await sleep(time);
console.log(`DONE ${i}`);
}
async function sleep(ms) {
return new Promise(resolve => setTimeout(resolve, ms));
}
Мы вызовем эту функцию внутри цикла for, а затем посмотрим на результат:
async function main() {
let items = [0, 1, 2, 3];
for(let i=0; i < items.length; i++) {
await getData(items[i], 1000 * (5 - items[i]));
}
console.log("END OF MAIN");
}
main().then();
Как вы видите, первый элемент должен выполняться дольше, а последний элемент займет меньше времени, потому что мы пропускаем время 1000 * (5 - items[i]), поэтому вывод будет таким:
DONE 0 DONE 1 DONE 2 DONE 3 END OF MAIN
Ключевое слово await гарантирует, что цикл приостанавливается до тех пор, пока обещание не будет разрешено, прежде чем перейти к следующей итерации, что позволяет нам последовательно обрабатывать элементы.
Использование async/await с циклом forEach
Объяснение: Цикл forEach — это встроенный в JavaScript метод работы с массивами, который перебирает каждый элемент массива.
let items = ['a', 'b', 'c'];
items.forEach(item => {
console.log(item);
});
// Output:
a
b
c
Хотя он обеспечивает краткий способ перебора массива, использование async/await в цикле forEach может иметь некоторые тонкие отличия по сравнению с использованием его с циклом for.
При использовании await в цикле forEach каждая итерация одновременно инициирует асинхронную операцию, не дожидаясь завершения предыдущей итерации. Это означает, что порядок выполнения асинхронных операций не гарантируется, что может привести к неупорядоченным результатам.
Мы выполним ту же функцию getData, но на этот раз с циклом forEach:
async function main() {
let items = [0, 1, 2, 3];
items.forEach(async item => {
await getData(item, 1000 * (5 - item));
});
console.log("END OF MAIN");
}
Поскольку все итерации инициированы и выполнение последнего элемента занимает меньше времени, это будет вывод:
END OF MAIN DONE 3 DONE 2 DONE 1 DONE 0
Вы видите, что первым регистрируемым сообщением является END OF MAIN, потому что даже последний элемент занимает 2 секунды, чтобы вернуть результат.
Рекомендации и варианты использования
При выборе между использованием async/await в цикле for или цикле forEach следует помнить о некоторых важных соображениях.
Использование async/await с циклом for подходит, когда:
- Последовательное выполнение и поддержание порядка асинхронных операций имеют решающее значение.
- Между итерациями существуют зависимости, когда результат одной итерации влияет на следующую.
С другой стороны, использование async/await с циклом forEach может быть более подходящим, когда:
- Порядок выполнения не важен, параллельное выполнение асинхронных операций допустимо.
- Каждая итерация независима и не зависит от результатов предыдущих итераций.
Важно отметить, что поведение async/await с циклом forEach может удивить из-за отсутствия упорядоченного выполнения. Если требуется сохранение порядка или обработка зависимостей между итерациями, лучше использовать цикл for.
В сценариях, где неупорядоченное выполнение допустимо или желательно, альтернативы, такие как использование Promise.all или использование библиотек, таких как async или Bluebird, могут обеспечить больший контроль и гибкость при работе с асинхронными операциями в цикле forEach.
Понимая эти различия, вы можете принимать обоснованные решения о том, какой подход использовать, исходя из конкретных требований и желаемых результатов.
Это была одна из обязательных тем в JavaScript, и в следующих блогах будет больше. А пока продолжайте программировать и следите за обновлениями🌟.