О том, как обрабатывать асинхронные итерации
Написание современного 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" | |
}] |
Заключение
Подводя итог, можно сказать, что все три метода способны обрабатывать итерации промисов, но немного отличаются по своему функционированию. Используйте for await of
, если порядок разрешения промисов важен для вас. Используйте Promise.all()
, если порядок не важен и вам нужно, чтобы все вызовы были успешными. Используйте Promise.allSettled()
, если порядок не важен и вам не обязательно, чтобы все отдельные вызовы были успешными.
Ресурсы
- Promise.all(): https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Promise/all
- Promise.allSettled(): https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Promise/allSettled
- Для await…of: https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Statements/for-await…of
- ПокеАпи: https://pokeapi.co/
Дополнительные материалы на PlainEnglish.io. Подпишитесь на нашу бесплатную еженедельную рассылку новостей. Подпишитесь на нас в Twitter и LinkedIn. Присоединяйтесь к нашему сообществу Discord.