- PVSM.RU - https://www.pvsm.ru -
Как запустить асинхронные циклы по порядку или параллельно в JavaScript?
Перед тем, как делать асинхронную магию, я хочу напомнить как выглядят классические синхронные циклы.
Очень давно я писал циклы таким способом (возможно вы тоже):
for (var i=0; i < array.length; i++) {
var item = array[i];
// делаем что-нибудь с item
}
Этот цикл хороший и быстрый. Но у него много проблем с читаемостью и с поддержкой. Через некоторое время я привык его лучшей версии:
array.forEach((item) => {
// делаем что-нибудь с item
});
Язык JavaScript развивается очень быстро. Появляются новые фичи и синтаксис. Одна из моих любых улучшений это async/await [1].
Сейчас я использую этот синтакс достаточно часто. И иногда встречаются ситуации, когда мне нужно что-либо сделать с элементами массива асинхронно.
Как использовать await
в теле циклы? Давайте просто попробуем написать асинхронную функцию и ожидать задачу обработки каждого элемента:
async function processArray(array) {
array.forEach(item => {
// тут мы определили синхронную анонимную функцию
// НО ЭТО КОД ВЫДАСТ ОШИБКУ!
await func(item);
})
}
Этот код выдаст ошибку. Почему? Потому что мы не можем использовать await
внутри синхронной функции. Как вы можете видеть processArray
— это асинхронная функция. Но анонимная функция, которую мы используем для forEach
, является синхронной.
Что можно с этим сделать?
Мы можем определить анонимную функцию как асинхронную:
async function processArray(array) {
array.forEach(async (item) => {
await func(item);
})
console.log('Done!');
}
Но forEach
не будет дожидаться выполнения завершения задачи. forEach
— синхронная операция. Она просто запустить задачи и пойдет дальше. Проверим на простом тесте:
function delay() {
return new Promise(resolve => setTimeout(resolve, 300));
}
async function delayedLog(item) {
// мы можем использовать await для Promise
// который возвращается из delay
await delay();
console.log(item);
}
async function processArray(array) {
array.forEach(async (item) => {
await func(item);
})
console.log('Done!');
}
processArray([1, 2, 3]);
В консоли мы увидим:
Done!
1
2
3
В некоторых ситуация это может быть нормальным результатом. Но всё же в большинстве вариантов это не подходящая логика.
Чтобы дождаться результата выполнения тела цикла нам нужно вернуться к старому доброму циклу "for". Но в этот раз мы будем использовать его новую версию с конструкцией for..of
(Спасибо Iteration Protocol [2]):
async function processArray(array) {
for (const item of array) {
await delayedLog(item);
})
console.log('Done!');
}
Это даст нам ожидаемый результат:
1
2
3
Done!
Каждый элемент массива будет обработан последовательно. Но мы может запустить цикл параллельно!
Нужно слегка изменить код, чтобы запустить операции параллельно:
async function processArray(array) {
// делаем "map" массива в промисы
const promises = array.map(delayedLog);
// ждем когда всё промисы будут выполнены
await Promise.all(promises);
console.log('Done!');
}
Этот код запустить несколько delayLog
задач параллельно. Но будьте аккуратны с большими массивами. Слишком много задач может быть слишком тяжело для CPU и памяти.
Так же, пожалуйста, не путайте "параллельные задачи" из примера с реальной параллельностью и потоками. Этот код не гарантирует параллельного исполнения. Всё завесит от тела цикла (в примере это delayedLog
). Запросы сети, webworkers и некоторые другие задачи могуть быть выполнены параллельно.
Автор: Антон Лаврёнов
Источник [3]
Сайт-источник PVSM.RU: https://www.pvsm.ru
Путь до страницы источника: https://www.pvsm.ru/javascript/304146
Ссылки в тексте:
[1] async/await: https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Statements/async_function
[2] Iteration Protocol: https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Iteration_protocols
[3] Источник: https://habr.com/post/435084/?utm_source=habrahabr&utm_medium=rss&utm_campaign=435084
Нажмите здесь для печати.