Promise.all, allSettled, race, any

Promise.all, allSettled, race, any #

У объекта Promise есть статические методы для координации нескольких асинхронных операций. Они принимают итерируемую коллекцию промисов (или значений, оборачиваемых в Promise.resolve) и возвращают один агрегирующий промис.


Promise.all — все или ничего #

Ждёт все входные промисы. При успехе — массив результатов в том же порядке, что и вход (не порядок завершения).

function fetchTrack(id) {
  const catalog = {
    a: { title: 'Aurora Line' },
    b: { title: 'Brick Echo' },
  }
  return new Promise((resolve, reject) => {
    setTimeout(() => {
      const item = catalog[id]
      if (!item) reject(new Error(`Нет трека ${id}`))
      else resolve(item)
    }, id === 'a' ? 100 : 250)
  })
}

Promise.all([fetchTrack('a'), fetchTrack('b')]).then(([first, second]) => {
  console.log(first.title, second.title) // "Aurora Line" "Brick Echo"
})

Первый же reject отклоняет весь Promise.all — остальные результаты теряются для потребителя.

Promise.all([fetchTrack('a'), fetchTrack('missing')])
  .then(() => {})
  .catch((err) => console.error(err.message)) // "Нет трека missing"

Пустой массив: Promise.all([]) сразу resolve([]).


Promise.allSettled — полный отчёт #

Никогда не отклоняется из-за одного сбоя; ждёт завершения каждого промиса:

const ids = ['a', 'missing', 'b']

Promise.allSettled(ids.map(fetchTrack)).then((results) => {
  for (const entry of results) {
    if (entry.status === 'fulfilled') {
      console.log('OK:', entry.value.title)
    } else {
      console.log('FAIL:', entry.reason.message)
    }
  }
})

Формат элемента:

ПолеПри успехеПри сбое
status'fulfilled''rejected'
данныеvaluereason

Удобно для дашбордов, пакетной загрузки обложек, отчётов «что удалось / что нет».


Promise.race — кто первый #

Резолвится или реджектится первым завершившимся промисом:

function timeout(ms) {
  return new Promise((_, reject) => {
    setTimeout(() => reject(new Error(`Таймаут ${ms} мс`)), ms)
  })
}

Promise.race([fetchTrack('b'), timeout(150)])
  .then((track) => console.log(track.title))
  .catch((err) => console.error(err.message)) // таймаут, если fetch медленнее

race не отменяет проигравшие промисы — они продолжают выполняться в фоне. Для реальной отмены нужен AbortController (отдельная тема).


Promise.any — первый успех #

Резолвится при первом fulfilled. Если все отклонены — AggregateError со списком причин:

const mirrors = [
  fetchTrack('missing1'),
  fetchTrack('missing2'),
  fetchTrack('a'),
]

Promise.any(mirrors)
  .then((track) => console.log('Зеркало ответило:', track.title))
  .catch((err) => {
    console.log(err instanceof AggregateError) // true при полном провале
    console.log(err.errors.length) // 2, если только два mirror были reject до успеха третьего
  })

Сценарий: несколько CDN или резервных эндпоинтов — достаточно одного рабочего.


Сравнение #

МетодУспех когдаПровал когда
allвсе fulfilledлюбой rejected
allSettledвсегда fulfilled (массив статусов)практически не reject
raceпервый settled (успех или сбой)первый reject, если он раньше успеха
anyпервый fulfilledвсе rejected → AggregateError

Практика: превью плейлиста #

async function loadPlaylistPreview(ids) {
  const settled = await Promise.allSettled(ids.map(fetchTrack))
  const tracks = settled
    .filter((r) => r.status === 'fulfilled')
    .map((r) => r.value)

  const failed = settled.length - tracks.length
  return { tracks, failed }
}

loadPlaylistPreview(['a', 'b', 'missing']).then(({ tracks, failed }) => {
  console.log(`Показано ${tracks.length}, ошибок: ${failed}`)
})

allSettled + фильтрация даёт частичный UI без падения всего экрана.


Итог #

  • Promise.all — параллельная загрузка с жёстким требованием успеха всех.
  • Promise.allSettled — инвентаризация исходов без раннего abort.
  • Promise.race — дедлайн или выбор самого быстрого ответа.
  • Promise.any — первый удавшийся источник среди нескольких.

См. также #

Часто спрашивают

Что выбрать — then или async/await?
async/await обычно читается проще для последовательного кода; then удобен для коротких цепочек и комбинирования с Promise API. Подробнее
Чем Promise.all отличается от allSettled?
all падает при первой ошибке; allSettled дожидается всех и отдаёт статус каждого результата. Подробнее