Промисы и async
Вопрос
В каких состояниях бывает промис и чем Promise.all отличается от allSettled и race?
Короткий ответ
Промис в состоянии pending, fulfilled или rejected. Состояние меняется один раз. then и catch возвращают новый промис.
Promise.allждёт все и падает на первой ошибке. Результаты в том же порядке, что вход.Promise.allSettledждёт все и отдаёт статус каждого, без короткого замыкания на ошибке.Promise.raceзавершается первым исходом, успехом или ошибкой.Promise.anyзавершается первым успехом и падаетAggregateError, если успешных нет.
async-функция всегда возвращает промис. throw внутри неё и отклонённый await становятся rejection. Ошибку ловят try/catch вокруг await или .catch у возвращённого промиса.
Разбор
await в цикле запускает запросы последовательно. Независимые запросы собирают в массив промисов и отдают в Promise.all:
const users = await Promise.all(ids.map((id) => fetchUser(id)))Гонка ответов: пользователь быстро меняет строку поиска, медленный старый запрос приходит позже и затирает новый результат. Лечится счётчиком поколения, AbortController или флагом «этот запрос ещё актуален».
async без await не делает код параллельным сам по себе. Два await подряд — это последовательность. Параллельность появляется, когда промисы созданы до ожидания.
На что смотрят на senior
На UI-задаче ждут отмену через AbortController у fetch, а не только игнорирование устаревшего ответа. Отмена освобождает сеть. Игнорирование только не даёт нарисовать старые данные, запрос при этом доезжает.
Ошибка в одном из параллельных запросов при Promise.all отменяет ожидание остальных для вызывающего кода, но сами запросы уже ушли. Если частичный результат полезен, берут allSettled и явно решают, что показать.
Необработанный rejection всплывает в unhandledrejection. В обработчике клика голый async без catch легко оставляет такую ошибку.
Типичные ошибки
- Ждать, что
Promise.allсохранит порядок завершения, а не порядок массива. Порядок результатов совпадает с порядком входа. - Оборачивать в
try/catchтолько синхронную часть и забыватьawait. - Считать
raceтаймаутом, не отменяя проигравший запрос.
Задача
Запросы к /a и /b падают по сети с разной задержкой. Что окажется в result, если использовать Promise.all, и что изменится на allSettled?
const result = await Promise.all([fetch('/a'), fetch('/b')])Показать ответ
Promise.all ждёт все и отклоняется на первой ошибке. Если /b упал, result не будет: await бросит, даже если /a уже успешно завершился.
Promise.allSettled дождётся обоих и вернёт массив { status: 'fulfilled' | 'rejected', ... } в том же порядке, что входной массив, не в порядке завершения.