Как обработать сразу несколько Promise, если один из них был отклонен?
Комментарии (1)
Ответ сгенерирован нейросетью и может содержать ошибки
Обработка нескольких Promise с отклоненными значениями
Работа с несколькими асинхронными операциями - частая задача в JavaScript. Существует несколько методов для обработки группы Promise, когда один из них может быть отклонен.
1. Promise.all() - все успешны или один отклонен
Promise.all() выполняет все Promise и возвращает результат, если все успешны, или отклонение при первой ошибке.
const promises = [
fetch('/api/users').then(r => r.json()),
fetch('/api/posts').then(r => r.json()),
fetch('/api/comments').then(r => r.json())
]
Promise.all(promises)
.then(([users, posts, comments]) => {
console.log('Все данные загружены:', users, posts, comments)
})
.catch(error => {
console.error('Ошибка при загрузке данных:', error)
})
Проблема: если один Promise отклоняется, все остальные игнорируются (даже если уже выполнены).
2. Promise.allSettled() - гарантирует получить результаты всех
Promise.allSettled() - современный подход. Ждёт выполнения всех Promise, независимо от успеха, и возвращает массив объектов со статусом.
const promises = [
fetch('/api/users').then(r => r.json()),
fetch('/api/posts').then(r => r.json()),
fetch('/api/comments').then(r => r.json())
]
Promise.allSettled(promises)
.then(results => {
results.forEach((result, index) => {
if (result.status === 'fulfilled') {
console.log(`Запрос ${index} успешен:`, result.value)
} else {
console.error(`Запрос ${index} провалился:`, result.reason)
}
})
})
Формат результата:
[
{ status: 'fulfilled', value: {...} },
{ status: 'rejected', reason: Error(...) },
{ status: 'fulfilled', value: {...} }
]
3. Promise.race() - первый результат (успех или ошибка)
Promise.race() возвращает результат первого завершённого Promise.
const promises = [
fetch('/api/fast-endpoint').then(r => r.json()),
new Promise((_, reject) =>
setTimeout(() => reject(new Error('Timeout')), 5000)
)
]
Promise.race(promises)
.then(data => console.log('Первый результат:', data))
.catch(error => console.error('Ошибка или timeout:', error))
Этот метод полезен для реализации таймаутов.
4. Promise.any() - первый успешный
Promise.any() ждёт первого успешного Promise. Если все отклоняются, возвращает AggregateError.
const mirrors = [
fetch('https://mirror1.com/file'),
fetch('https://mirror2.com/file'),
fetch('https://mirror3.com/file')
]
Promise.any(mirrors)
.then(response => console.log('Загружено с:', response.url))
.catch(error => {
console.error('Все зеркала недоступны')
console.error('Причины:', error.errors)
})
5. Обработка с try/catch и async/await
Модерный способ с условной обработкой ошибок:
async function loadAllData() {
const results = await Promise.allSettled([
fetch('/api/users').then(r => r.json()),
fetch('/api/posts').then(r => r.json()),
fetch('/api/comments').then(r => r.json())
])
const data = {}
const errors = {}
results.forEach((result, index) => {
if (result.status === 'fulfilled') {
data[index] = result.value
} else {
errors[index] = result.reason.message
}
})
return { data, errors }
}
try {
const { data, errors } = await loadAllData()
if (Object.keys(errors).length > 0) {
console.warn('Некоторые запросы провалились:', errors)
}
console.log('Успешные данные:', data)
} catch (error) {
console.error('Критическая ошибка:', error)
}
6. Обработка с условной логикой
Иногда нужно разные сценарии для разных ошибок:
function loadDataWithFallbacks() {
return Promise.allSettled([
fetch('/api/primary').then(r => r.json()),
fetch('/api/backup').then(r => r.json())
])
.then(results => {
const primary = results[0]
const backup = results[1]
// Если основной успешен - используем его
if (primary.status === 'fulfilled') {
return primary.value
}
// Если основной провалился, используем резервный
if (backup.status === 'fulfilled') {
console.warn('Основной источник недоступен, используем резервный')
return backup.value
}
// Если оба провалились - выбрасываем ошибку
throw new Error('Оба источника данных недоступны')
})
}
7. Обработка с timeout
Часто нужно отменить Promise если он выполняется слишком долго:
function withTimeout(promise, ms) {
const timeout = new Promise((_, reject) =>
setTimeout(() => reject(new Error('Timeout')), ms)
)
return Promise.race([promise, timeout])
}
async function loadWithTimeout() {
try {
const data = await withTimeout(
fetch('/api/slow-endpoint').then(r => r.json()),
5000 // 5 секунд
)
console.log('Данные загружены:', data)
} catch (error) {
console.error('Ошибка или timeout:', error.message)
}
}
8. Использование AbortController для отмены
Для более точного контроля используйте AbortController:
const controller = new AbortController()
const timeoutId = setTimeout(() => controller.abort(), 5000)
fetch('/api/data', { signal: controller.signal })
.then(r => r.json())
.then(data => console.log(data))
.catch(error => {
if (error.name === 'AbortError') {
console.error('Запрос отменён по timeout')
} else {
console.error('Ошибка запроса:', error)
}
})
.finally(() => clearTimeout(timeoutId))
Сравнение методов
| Метод | Возвращает | При ошибке | Использование |
|---|---|---|---|
| Promise.all() | Массив результатов | Отклонение на первой ошибке | Все должны успеть |
| Promise.allSettled() | Массив объектов {status, value/reason} | Не выбрасывает | Частичные данные OK |
| Promise.race() | Первый результат | Первая ошибка | Таймауты, гонки |
| Promise.any() | Первый успешный | AggregateError если все ошибки | Зеркала, fallbacks |
Рекомендации
- Promise.allSettled() - лучший выбор большинства случаев, когда нужны все данные
- Promise.all() - только если все Promise критичны и ошибка одного блокирует всё
- Promise.any() - для fallback сценариев (несколько источников данных)
- Promise.race() - для таймаутов и гонок
- async/await + try/catch - самый читаемый и современный синтаксис
- Всегда обрабатывайте ошибки явно - это критично для UX
Выбор метода зависит от того, нужны ли вам частичные данные при ошибках или нужно дождаться успеха всех операций.