Skip to Content
JavaScriptПромисы и async

Промисы и async

Уровень: middle.

Вопрос

В каких состояниях бывает промис и чем 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', ... } в том же порядке, что входной массив, не в порядке завершения.

Last updated on