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' |
| данные | value | reason |
Удобно для дашбордов, пакетной загрузки обложек, отчётов «что удалось / что нет».
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— первый удавшийся источник среди нескольких.