> Как обрабатывать случаи отклонения одного из промисов в Promise.all в JavaScript? (JavaScript)

Уровень: senior · Роль: frontend · Категория: Технические вопросы

Компании: EPAM, Яндекс

Стек: JavaScript

> Пример ответа

Короткий ответ

Promise.all отклоняется сразу при первом rejected promise, игнорируя остальные. Для обработки ошибок используйте Promise.allSettled, если нужны все результаты, или оберните каждый промис в .catch, чтобы преобразовать rejection в resolved значение. В async/await - try/catch вокруг await Promise.all(...). Для частичной обработки примените Promise.all(promises.map(p => p.catch(err => err))).

Подробное объяснение

Promise.all реализует стратегию fail-fast: при первом же rejection весь комбинированный промис отклоняется, а остальные промисы продолжают выполнение, но их результаты теряются. Это критично, когда нужно дождаться всех операций независимо от ошибок.

Основные подходы:

  • Promise.allSettled (ES2020) - возвращает массив объектов с status: "fulfilled" или "rejected" и соответствующими value/reason. Идеально для независимых запросов.
  • Обёртка с catch - каждый промис обрабатывает ошибку локально, возвращая fallback-значение или null. Позволяет сохранить структуру Promise.all.
  • Promise.all с map - promises.map(p => p.catch(e => e)) - простой способ не допустить rejection.
  • try/catch в async/await - если нужно прервать выполнение при первой ошибке, но корректно её обработать.

Для частичной обработки (например, 3 из 5 успешных) потребуется кастомная логика: отслеживать количество resolved/rejected через счётчики или использовать библиотеки типа p-retry.

На практике

В реальных проектах выбор зависит от сценария:

  • Загрузка данных для UI: Promise.allSettled - показываем частичные данные, логируем ошибки.
  • Критическая цепочка (например, оплата + отправка email): Promise.all с try/catch - любая ошибка отменяет всё.
  • Параллельные валидации: обёртка с catch возвращает { valid: false, error }, сохраняя единый формат.
  • Для старых браузеров - полифиллы или fallback на Promise.all с ручной обработкой.

Важно помнить: Promise.all не отменяет остальные промисы при ошибке - они продолжают выполняться в фоне. Для реальной отмены нужны AbortController или кастомные флаги.

Пример кода

JAVASCRIPT
// 1. Promise.allSettled - все результаты
const results = await Promise.allSettled([
fetch('/api/users'),
fetch('/api/posts'),
fetch('/api/error') // упадёт
]);
const data = results
.filter(r => r.status === 'fulfilled')
.map(r => r.value);
// 2. Обёртка с catch - fallback
const safeFetch = (url) =>
fetch(url).catch(() => ({ error: true, url }));
const [users, posts] = await Promise.all([
safeFetch('/api/users'),
safeFetch('/api/posts')
]);
// 3. Кастомная обработка с частичным успехом
const promises = urls.map(url => fetch(url));
const results = await Promise.all(
promises.map(p => p.then(
value => ({ status: 'fulfilled', value }),
reason => ({ status: 'rejected', reason })
))
);
const successful = results.filter(r => r.status === 'fulfilled');

Как отвечать на собеседовании

Начните с чёткого объяснения fail-fast поведения Promise.all. Затем перечислите альтернативы, акцентируя trade-off между простотой и контролем. Приведите конкретный сценарий из опыта, где вы выбрали Promise.allSettled вместо Promise.all. Упомяните, что промисы не отменяются автоматически - это частый источник багов. Если спросят про производительность, отметьте, что все промисы запускаются параллельно, разница только в обработке результатов.

Что проверяет интервьюер

  • Понимание fundamental difference между Promise.all и Promise.allSettled.
  • Умение выбирать правильный инструмент под задачу, а не заученный шаблон.
  • Знание edge cases: потеря результатов, неотменяемые промисы, обработка ошибок в async/await.
  • Способность проектировать отказоустойчивые параллельные операции.
  • Понимание, что Promise.all - это не про отмену, а про композицию.

Типичные ошибки

  • Использование Promise.all для независимых запросов, где нужны все результаты - теряются данные при одной ошибке.
  • Забывание, что rejected промис не останавливает выполнение других - утечки ресурсов или побочные эффекты.
  • Путаница между Promise.all и Promise.race - разное поведение при rejection.
  • Отсутствие обработки ошибок в .catch внутри map - если catch сам выбрасывает исключение.
  • Предположение, что Promise.allSettled доступен везде без полифилла.

> ГОТОВЫ К СЛЕДУЮЩЕМУ СОБЕСЕДОВАНИЮ?

Запустите тренировочную сессию с ИИ и получите детальную обратную связь, чтобы увереннее проходить реальные интервью