О том, как обрабатывать асинхронные итерации

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

Чтобы помочь вам правильно прицелиться, я рассмотрю три метода обработки итерации обещаний: Promise.all() , Promise.allSettled() и for await...of .

Для ожидания… из

Асинхронный For await...of очень похож на своего синхронного брата for of: вы можете перебрать итерируемый объект, например Array, Map или Set. For await of отличается возможностью обработки асинхронных кодов внутри цикла.

Итак, давайте предположим, что у нас есть массив с идентификаторами 5 покемонов. Используя for await…of, мы можем перебрать идентификаторы один за другим и получить имя связанного покемона с помощью PokéApi. Если в одном из вызовов возникает ошибка, мы можем ее перехватить и обработать, чтобы впоследствии продолжить оставшуюся часть цикла.

Обещание.все()

Итак, как нам получить имена покемонов, используя Promise.all()? Эта функция принимает итерацию промисов в качестве входного параметра. Таким образом, мы можем .map перебрать идентификаторы, получить имена и предоставить асинхронную карту в качестве входных данных для Promise.all . Когда все обещания выполнены, функция возвращает массив, содержащий результат всех обещаний.

Основное различие между «for await…of» и «Promise.all()»

Итак, кроме небольшой разницы в возвращаемом значении, в чем большая разница между for await...of и Promise.all() . Самое важное отличие состоит в том, что Promise.all() обрабатывает входные промисы одновременно, а for await...of разрешает их по одному. Так, например, используя for await...of, мы сможем прекратить вызов pokeApi после того, как найдем Charmander, а Promise.all() создадим все промисы одновременно и завершим их только тогда, когда все они будут разрешены.

Используя for await...of, вы получаете более детальный контроль над промисами. Поэтому, если порядок выполнения промисов важен для вас, for await...of — ваш предпочтительный выбор. Однако повышенный контроль не является бесплатным. Тот факт, что for await...of обрабатывает промисы одно за другим, делает его намного медленнее. На самом деле, в случае нашего получения покемонов, почти в два раза медленнее, как показано на изображении ниже. Поэтому, как правило, я рекомендую использовать Promise.all() и переключаться на for await...of только в том случае, если одновременная обработка промисов вызывает проблемы.

«Promise.all()» против «Promise.allSettled()»

Все вызовы, которые мы сделали в PokéApi до сих пор, были успешными. Но что, если при получении одного из покемонов произойдет ошибка? Используя for await of, мы ловим ошибку на каждой итерации. Так что, если, например, получение покемона с идентификатором 2 не удалось, мы можем обработать ошибку и перейти к следующей итерации.

Напротив, Promise.all() по умолчанию быстро завершается ошибкой, то есть все промис отклоняется в случае сбоя вызова. Поэтому, если при получении третьего покемона возникает ошибка, выполнение останавливается, и мы получаем сообщение об ошибке.

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

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

В этих ситуациях Promise.allSettled() является более разумным выбором. Эта функция очень похожа на Promise.all(), но вместо быстрого отказа она возвращает промис, который разрешается после того, как все заданные промисы либо выполнены, либо отклонены. Результат предыдущего примера с ошибочным вызовом по адресу index === 2 будет выглядеть следующим образом с использованием Promise.allSettled(). Мы видим, что четыре вызова выполнены, но вызов с индексом 2 был отклонен.

[{
status: "fulfilled",
value: "bulbasaur"
}, {
status: "fulfilled",
value: "ivysaur"
}, {
reason: 'something went wrong!',
status: "rejected"
}, {
status: "fulfilled",
value: "charmander"
}, {
status: "fulfilled",
value: "charmeleon"
}]
view raw error.js hosted with ❤ by GitHub

Заключение

Подводя итог, можно сказать, что все три метода способны обрабатывать итерации промисов, но немного отличаются по своему функционированию. Используйте for await of, если порядок разрешения промисов важен для вас. Используйте Promise.all(), если порядок не важен и вам нужно, чтобы все вызовы были успешными. Используйте Promise.allSettled(), если порядок не важен и вам не обязательно, чтобы все отдельные вызовы были успешными.

Ресурсы

Дополнительные материалы на PlainEnglish.io. Подпишитесь на нашу бесплатную еженедельную рассылку новостей. Подпишитесь на нас в Twitter и LinkedIn. Присоединяйтесь к нашему сообществу Discord.