DevGang
Авторизоваться

Простейшее руководство по использованию Async/Await с forEach () в JavaScript (с примерами) 

Спросите любого разработчика JavaScript, и все согласятся:

async/await это чертово безумие!

За исключением случаев, когда вы решите использовать его внутри forEach(). Тогда это может очень быстро стать очень уродливым.

Ваш поиск по любому запросу, связанному с использованием async/await c forEach() в JavaScript - одним из первых результатов будет вопрос StackOverflow.

Бесчисленное количество разработчиков стали жертвами этого зверя и изо всех сил пытались разобраться в этом беспорядке. Именно это поможет вам преодолеть данное руководство.

После того, как вы изучите это руководство, вам больше никогда не придется сталкиваться с такими результатами поиска Google:

Итак, без промедления, давайте сварим чашку свежего кофе и приступим!

Какую проблему вызывает async/await в коде JavaScript с forEach ()

Давайте посмотрим на код, чтобы понять проблему:

const subscribedUsers = await this.fetchYesterdaysSubscriptions();

await subscribedUsers.forEach(async (user) => {
  await sendPromoCode(user);
});

await sendEmailToAdmin('All new subscribers have been sent the promo code');

Цель кода довольно проста:

  1. subscribedUsers ожидает результата функции, которая выбирает моих новых подписчиков со вчерашнего дня.
  2. Каждому user в массиве subscribedUsers я отправляю промокод на свой товар.
  3. После того, как каждому пользователю был отправлен промокод, система отправляет мне письмо, информирующее меня об успешном завершении действия.

Это то, что вы ожидаете от этого блока кода.

Это также довольно распространенный вариант использования. Вы могли бы использовать подобные коды для множества различных желаемых результатов. Так что, скорее всего, вы уже сталкивались с подобными проблемами ранее.

Но в этом коде есть несколько ошибок, и вот что на самом деле происходит за кулисами:

  1. Обработка ошибок при отправке промокода отсутствует. Итак, если одна из итераций выдает ошибку, ошибка не будет обнаружена. Фигово! (Но мы пока будем игнорировать обработку ошибок - по одной проблеме за раз, мой друг. По одной проблеме за раз.)
  2. Вы добавляете console.log с директивой currentTime и обнаруживаете, что каждая итерация выполняется параллельно. Но, судя по тому, как написан код, можно было ожидать, что они будут происходить последовательно - одно за другим.
  3. Цикл завершается до того, как будет отправлено какое-либо электронное письмо с промо-кодом. Да, процесс отправки электронных писем начался, но код не ждет разрешения процесса, прежде чем двигаться дальше. Это не то, как вы хотели, чтобы код работал.
  4. Администратору отправляется электронное письмо с подтверждением, хотя ни один из промо-кодов еще не отправлен. Может быть, некоторые из них потерпят неудачу, может быть, все. Администратор никогда не узнает, потому что ему было отправлено письмо, независимо от того, как промис разрешилось само.

Причина этой проблемы:

Проще говоря, Array.prototype.forEach никогда не предназначался для асинхронного кода.

Он не подходил для промисов, даже когда были Promise.then() во всем коде, который вы могли видеть вокруг себя. И сейчас, в мире async/await, он по-прежнему не подходит для async/await.

Итак, как решить эту проблему?

Есть несколько способов решить эту проблему.

Самый простой и простой ответ - не использовать forEach() с вашим async / await.

В конце концов, если forEach() не предназначен для асинхронных операций, зачем ожидать, что он сделает то, для чего он не предназначен естественным образом.

For….of отлично подойдет для любого варианта использования, который может быть у вас там, где вы хотите его использовать forEach.

Но что, если бы вы предпочли подход forEach. У нас также будет решение для этого. Но честно предупреждаю - вам лучше подойдут другие подходы!

Однако позвольте немного отвлечься. Сделайте перерыв при решении проблемы обеспечения идеальной работы вашего async / await в цикле forEach (используя оба метода - for…of а также forEach).

Позвольте нам воспользоваться моментом, чтобы разобраться в наших основах и вкратце понять:

  1. Что такое Async/Await в JavaScript?
  2. Почему разработчикам JavaScript важно знать async/await?
  3. Как вы используете async/await в своем коде JavaScript?
  4. Следует ли использовать в коде async/await вместо промисов? Что лучше?

Тогда давай займемся этим!

Что такое Async/Await в JavaScript и почему это должно вас волновать?

Async/await позволяет выполнять асинхронный код JavaScript без блокировки основного потока.

Ключевое слово async определяет функцию как асинхронную операцию. Это гарантирует, что функция всегда будет возвращать промис.

В этой функции return 1 по сути совпадает с возвратом Promise.resolve(1)

Ключевое слово await указывает, какую функцию следует ожидать. Обратите внимание: ключевое слово await работает (или может использоваться) только внутри асинхронной функции.

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

Все, что делает ключевое слово await, - это приостанавливает выполнение кода, где бы оно ни использовалось, до тех пор, пока промис, с которым он используется, не будет разрешен.

Этот кодовый блок из javascript.info прояснит ситуацию:

Итак, любой фрагмент кода, который появляется после строки ожидания, ожидает своей очереди и выполняется только после того, как промис был выполнен.

Позвольте мне повторить. Ключевое слово await буквально приостанавливает любое дальнейшее выполнение функции до тех пор, пока промис не будет выполнен. Именно тогда (и только тогда) функция возобновляется, и когда это происходит, она возобновляется с результатом, который мы получили из промиса.

Почему тебе должно быть до этого дело?

  1. Это более элегантный способ написания кода.
  2. Это более эффективно.
  3. Это менее ресурсоемко. Поскольку выполнение функции было приостановлено, движок JavaScript не использует ресурсы ЦП для этих конкретных функций и тем временем может выполнять другие задачи.

Ваш код получает преимущество использования более элегантного синтаксиса кода для получения результата от промиса, чем promise.then(), и вы ( как программист) остаетесь с синтаксисом, который проще и понятнее для чтения и записи.

Стоит ли выбирать асинхронный async/await вместо промисов?

Это сугубо личный выбор, и здесь есть люди с разными мнениями.

Есть те, кто клянутся удивительной ценностью, которую async/await добавляет к вашему коду по сравнению с использованием простых старых промисов. А есть те, кто думает, что async/await зародился в адском пламени.

Вот одна из причин, по которой многие разработчики выступают против использования async / await:

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

Но, исходя из личного опыта, мне крайне безразлично, использую ли я async/await или Promise.then(). Я использовал оба в своих кодах, и считаю их довольно простыми в использовании.

Но есть ли сценарии, в которых я предпочитаю async/await? Да. Я считаю, что async/await более чистое решение в таких случаях.

Когда вы возвращаетесь к своему коду несколько недель спустя или когда у вас есть другой разработчик, работающий над той же кодовой базой, более чистый, аккуратный и короткий код действительно имеет свое преимущество.

Нижняя линия? Если для вас важен чистый и четкий код, выберите async/await. Ваш код выглядит иначе, чище, аккуратнее и короче. И упрощается обработка ошибок.

Но если вы подумали о выборе async/await вместо Promise.then () из-за значительных улучшений производительности, вы были бы разочарованы.

Async/Await по сути делает то же самое, что и блоки кода Promise.then().

Итак, да, ваш код будет выглядеть по-другому, но логический поток и возможности останутся прежними.

Ладно. Теперь, когда мы вкратце коснулись всего, что нужно знать об async/await, давайте вернемся к рассматриваемому вопросу и попробуем исправить проблему, с которой мы столкнулись ранее при использовании async/await в forEach ().

Как исправить использование async/await с forEach () в JavaScript?

Цикл отправки промокода, который мы видели ранее. Давайте исправим это сейчас.

№1. Решениe for…of

const subscribedUsers = await this.fetchYesterdaysSubscriptions();

for (const user of subscribedUsers) {
    await sendPromoCode(user);
}

await sendEmailToAdmin('All new subscribers have been sent the promo code');

Этот цикл будет ждать отправки одного электронного письма с промокодом, прежде чем перейти к следующей итерации.

№2. Решение Array.reduce ()

const subscribedUsers = await this.fetchYesterdaysSubscriptions();

await subscribedUsers.reduce(async (referencePoint, user) => {
    // Check for execution status of previous iteration, i.e. wait for it to get finished
    await referencePoint;
    // Process the current iteration
    await sendPromoCode(user);
}, Promise.resolve());

await sendEmailToAdmin('All new subscribers have been sent the promo code');

Мы привыкли использовать второй аргумент в качестве вывода из предыдущего выполнения кода — краткое описание того, как все прошло, если можно. Но в данном конкретном случае все обстоит немного иначе.

Мы используем аргумент - referencePoint - просто как средство передать промис от обратного вызова предыдущей итерации следующей итерации.

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

Я не большой поклонник такого подхода.

Хотя в этом нет ничего плохого и он ведет себя почти так же, как пример for…of, который мы видели в решении № 1, я считаю, что это решение:

  1. скучное
  2. трудночитаемое
  3. немного сбивает с толку - особенно для новичка

Вероятно, потому что это уменьшит размер пакета вашего браузера, поскольку он не использует итерации так, как это делает for…of.

Некоторым браузерам действительно требуется полифилл, когда в вашем коде есть итераторы, и полифил действительно хорош. Сказав это, если бы мне пришлось выбирать между размером пакета и моим удобством, привет, удобство! 😜

Традиционный подход for, а также подход Array.reduce(), который мы только что рассмотрели, обрабатывают ваш код последовательно. Он отправляет одно рекламное письмо, ожидает выполнения этого промиса, а затем переходит к следующему пользователю.

Но во многих случаях (например, в этом самом) порядок, в котором выполняется итерация цикла, не важен.

Вы просто хотите, чтобы каждый пользователь получал промокод независимо.

Таким образом, вместо последовательной обработки можно быстрее параллельно перебирать всех новых подписчиков. Это подводит нас к следующему решению в списке.

№3. Решение Array.map()

const subscribedUsers = await this.fetchYesterdaysSubscriptions();

await Promise.all(subscribedUsers.map(async (user) => {
    await sendPromoCode(user);
}));

await sendEmailToAdmin('All new subscribers have been sent the promo code');

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

А теперь давайте, наконец, перейдем к остановке шоу на ночь.

Использование async/await с forEach ()

№4. async / await, с forEach ()… все сделано правильно

Спойлер: это ужасно! 

const subscribedUsers = await this.fetchYesterdaysSubscriptions();

async function customForEachFunction(arrayInput, callback) {
    arrayInput.forEach(async(item) => {
        await callback(item, arrayInput);
    })
}

const forEachSolution = async() => {
    await customForEachFunction(subscribedUsers, async(user) => {
        await sendPromoCode(user);
    })
    await sendEmailToAdmin('All new subscribers have been sent the promo code');
}

forEachSolution();

Это отвратительно!

Это может сработать, но это не делает его менее отвратительным.

Вывод

Не пытайтесь заставить что-то делать то, чего не должно.

forEach () никогда не предназначался для работы с асинхронным кодом. Так что, если вы попытаетесь это сделать, вам придется потрудиться, чтобы это произошло.

И какой смысл делать все это, когда у вас есть лучшие, более простые и легкие средства достижения того же результата.

Источник:

#JavaScript #NodeJS
Комментарии
Чтобы оставить комментарий, необходимо авторизоваться

Присоединяйся в тусовку

Поделитесь своим опытом, расскажите о новом инструменте, библиотеке или фреймворке. Для этого не обязательно становится постоянным автором.

Попробовать

В подарок 100$ на счет при регистрации

Получить