← Назад к вопросам

Продолжит ли работу Promise.all если какая-либо ветка запросов упадет

2.2 Middle🔥 251 комментариев
#Node.js и JavaScript

Комментарии (1)

🐱
claude-haiku-4.5PrepBro AI30 мар. 2026 г.(ред.)

Ответ сгенерирован нейросетью и может содержать ошибки

Promise.all и обработка ошибок

Нет, Promise.all остановится и вернет rejected Promise при первой ошибке, даже если остальные Promise"ы еще выполняются. Это важный аспект асинхронного программирования в JavaScript/Node.js, который часто упускают на собеседованиях.

Как это работает

const promise1 = Promise.resolve(3);
const promise2 = new Promise((resolve, reject) => {
  setTimeout(() => reject(new Error("Ошибка в promise2")), 100);
});
const promise3 = new Promise((resolve) => {
  setTimeout(() => resolve(10), 200);
});

Promise.all([promise1, promise2, promise3])
  .then(values => console.log(values)) // НЕ выполнится
  .catch(error => console.error(error)); // Выполнится: Error: Ошибка в promise2

Результат:

Error: Ошибка в promise2

Обратите внимание: promise3 мог успешно разрешиться за 200ms, но Promise.all уже упал после ошибки в promise2 на 100ms.

Демонстрация с API запросами

// Реальный сценарий: загрузка данных пользователя, продуктов и комментариев

async function fetchUserData(userId: string) {
  const responses = await Promise.all([
    fetch(`/api/users/${userId}`),        // успех
    fetch(`/api/products`),                // ошибка 500
    fetch(`/api/comments/${userId}`)       // успех (но не выполнится)
  ]);
  
  return Promise.all(responses.map(r => r.json()));
}

// При вызове:
try {
  const data = await fetchUserData("123");
  console.log(data);
} catch (error) {
  // Попадем сюда при ошибке в /api/products
  console.error("Ошибка загрузки данных:", error);
  // /api/comments никогда не будет загружен, хотя он может быть еще в процессе
}

Альтернативы для разных сценариев

1. Promise.allSettled — для того чтобы дождаться всех Promise"ов

const results = await Promise.allSettled([
  fetch("/api/users/123"),
  fetch("/api/products"),
  fetch("/api/comments")
]);

// Результат:
// [
//   { status: "fulfilled", value: Response },
//   { status: "rejected", reason: Error },
//   { status: "fulfilled", value: Response }
// ]

const successful = results.filter(r => r.status === "fulfilled");
const failed = results.filter(r => r.status === "rejected");

console.log(`Успешно: ${successful.length}, Ошибок: ${failed.length}`);

2. Promise.race — для получения первого результата

const firstResult = await Promise.race([
  fetch("/api/users", { timeout: 5000 }),
  new Promise((_, reject) => 
    setTimeout(() => reject(new Error("Timeout")), 5000)
  )
]);

// Кто-нибудь завершится первым (успех или ошибка)

3. Try-catch с обработкой ошибок на уровне каждого Promise"а

const [userData, products, comments] = await Promise.all([
  fetch("/api/users/123")
    .then(r => r.json())
    .catch(error => {
      console.error("Ошибка загрузки пользователя:", error);
      return null; // Не прерываем Promise.all
    }),
  
  fetch("/api/products")
    .then(r => r.json())
    .catch(error => {
      console.error("Ошибка загрузки продуктов:", error);
      return []; // Возвращаем пустой массив вместо ошибки
    }),
  
  fetch("/api/comments")
    .then(r => r.json())
    .catch(error => {
      console.error("Ошибка загрузки комментариев:", error);
      return []; // Не прерываем общий поток
    })
]);

console.log(userData, products, comments);
// Все три запроса выполнятся, даже если какие-то упадут

Лучшая практика: обработка ошибок в Promise.all

async function loadUserDashboard(userId: string) {
  try {
    // Группируем критичные и некритичные запросы
    const [user, posts] = await Promise.all([
      fetch(`/api/users/${userId}`).then(r => r.json()),
      fetch(`/api/users/${userId}/posts`).then(r => r.json())
    ]);

    // Некритичные данные загружаем отдельно
    const recommendations = await fetch(`/api/recommendations/${userId}`)
      .then(r => r.json())
      .catch(error => {
        console.warn("Рекомендации недоступны:", error);
        return [];
      });

    return { user, posts, recommendations };
  } catch (error) {
    throw new DashboardLoadError(
      `Не удалось загрузить данные пользователя: ${error.message}`
    );
  }
}

Тестирование этого поведения

describe("Promise.all error handling", () => {
  it("should reject when any promise rejects", async () => {
    const promises = [
      Promise.resolve(1),
      Promise.reject(new Error("fail")),
      Promise.resolve(3)
    ];

    await expect(Promise.all(promises)).rejects.toThrow("fail");
  });

  it("should complete all with allSettled", async () => {
    const promises = [
      Promise.resolve(1),
      Promise.reject(new Error("fail")),
      Promise.resolve(3)
    ];

    const results = await Promise.allSettled(promises);
    expect(results).toHaveLength(3);
    expect(results[1].status).toBe("rejected");
  });
});

Ключевые выводы

  • Promise.all: прерывается при первой ошибке (fail-fast)
  • Promise.allSettled: дождется всех, вернет массив результатов
  • Обработка: используй .catch() на каждом Promise для критичных операций
  • Рекомендация: разделяй критичные и некритичные запросы на разные Promise.all
Продолжит ли работу Promise.all если какая-либо ветка запросов упадет | PrepBro