Хабрахабр

[Перевод] Как работать с async/await в циклах JavaScript

Как запустить асинхронные циклы по порядку или параллельно в JavaScript?

Перед тем, как делать асинхронную магию, я хочу напомнить как выглядят классические синхронные циклы.

Синхронные циклы

Очень давно я писал циклы таким способом (возможно вы тоже):

for (var i=0; i < array.length; i++) { var item = array[i]; // делаем что-нибудь с item
}

Но у него много проблем с читаемостью и с поддержкой. Этот цикл хороший и быстрый. Через некоторое время я привык его лучшей версии:

array.forEach((item) => { // делаем что-нибудь с item
});

Появляются новые фичи и синтаксис. Язык JavaScript развивается очень быстро. Одна из моих любых улучшений это async/await.

И иногда встречаются ситуации, когда мне нужно что-либо сделать с элементами массива асинхронно. Сейчас я использую этот синтакс достаточно часто.

Асинхронные циклы

Давайте просто попробуем написать асинхронную функцию и ожидать задачу обработки каждого элемента: Как использовать await в теле циклы?

async function processArray(array) )
}

Почему? Этот код выдаст ошибку. Как вы можете видеть processArray — это асинхронная функция. Потому что мы не можем использовать await внутри синхронной функции. Но анонимная функция, которую мы используем для forEach, является синхронной.

Что можно с этим сделать?

1. Не дожидаться результата выполнения

Мы можем определить анонимную функцию как асинхронную:

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

Но всё же в большинстве вариантов это не подходящая логика. В некоторых ситуация это может быть нормальным результатом.

2. Обработка цикла последовательно

Но в этот раз мы будем использовать его новую версию с конструкцией for..of (Спасибо Iteration Protocol): Чтобы дождаться результата выполнения тела цикла нам нужно вернуться к старому доброму циклу "for".

async function processArray(array) { for (const item of array) { await delayedLog(item); }) console.log('Done!');
}

Это даст нам ожидаемый результат:

1
2
3
Done!

Но мы может запустить цикл параллельно! Каждый элемент массива будет обработан последовательно.

3. Обработка цикла параллельно

Нужно слегка изменить код, чтобы запустить операции параллельно:

async function processArray(array) { // делаем "map" массива в промисы const promises = array.map(delayedLog); // ждем когда всё промисы будут выполнены await Promise.all(promises); console.log('Done!');
}

Но будьте аккуратны с большими массивами. Этот код запустить несколько delayLog задач параллельно. Слишком много задач может быть слишком тяжело для CPU и памяти.

Этот код не гарантирует параллельного исполнения. Так же, пожалуйста, не путайте "параллельные задачи" из примера с реальной параллельностью и потоками. Запросы сети, webworkers и некоторые другие задачи могуть быть выполнены параллельно. Всё завесит от тела цикла (в примере это delayedLog).

Показать больше

Похожие публикации

Добавить комментарий

Ваш адрес email не будет опубликован. Обязательные поля помечены *

Кнопка «Наверх»