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

Как работает Promise.all?

1.0 Junior🔥 231 комментариев
#JavaScript Core

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

🐱
claude-haiku-4.5PrepBro AI3 апр. 2026 г.(ред.)

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

Как работает Promise.all

Определение и назначение

Promise.all() — это статический метод, который принимает массив Promise и возвращает новый Promise, который разрешается когда ВСЕ Promise в массиве разрешены, или отклоняется если хотя бы ОДИН Promise отклонен.

Базовая структура

Promise.all([promise1, promise2, promise3])
  .then(results => {
    // results = [result1, result2, result3]
    // Вызвётся только если все promise разрешены
  })
  .catch(error => {
    // Вызовется если хотя бы один promise отклонен
  });

Ключевые особенности

1. Параллельное выполнение

Основное отличие от последовательного выполнения — все Promise выполняются ОДНОВРЕМЕННО, а не один за другим:

// Неправильно — последовательно (времени: 1 + 2 + 3 = 6 секунд)
const result1 = await fetchUser(1); // 1 сек
const result2 = await fetchUser(2); // 2 сек
const result3 = await fetchUser(3); // 3 сек

// Правильно — параллельно (времени: max(1, 2, 3) = 3 секунды)
const [result1, result2, result3] = await Promise.all([
  fetchUser(1),
  fetchUser(2),
  fetchUser(3)
]);

Время выполнения = времени самого долгого Promise, а не сумме всех!

2. Сохранение порядка результатов

Результаты возвращаются в том же порядке, что и Promise в исходном массиве, даже если они разрешаются в другом порядке:

const promises = [
  new Promise(resolve => setTimeout(() => resolve('A'), 300)),
  new Promise(resolve => setTimeout(() => resolve('B'), 100)),
  new Promise(resolve => setTimeout(() => resolve('C'), 200))
];

Promise.all(promises).then(results => {
  console.log(results); // ['A', 'B', 'C'] — порядок сохранён!
});

// Порядок разрешения: B (100ms) -> C (200ms) -> A (300ms)
// Но результаты: ['A', 'B', 'C']

3. Fail-fast поведение

Если хотя бы один Promise отклонен, весь Promise.all немедленно отклоняется:

const promises = [
  Promise.resolve('Success 1'),
  Promise.reject('Error!'),
  Promise.resolve('Success 2')
];

Promise.all(promises)
  .then(results => {
    console.log('Never called');
  })
  .catch(error => {
    console.log(error); // 'Error!'
  });

// Это произойдёт, даже если бы был setTimeout(3000) на третий Promise

Практические примеры

Пример 1: Загрузка нескольких ресурсов

function fetchUserData(userId) {
  return Promise.all([
    fetch(`/api/users/${userId}`).then(r => r.json()),
    fetch(`/api/users/${userId}/posts`).then(r => r.json()),
    fetch(`/api/users/${userId}/comments`).then(r => r.json())
  ]);
}

fetchUserData(1).then(([user, posts, comments]) => {
  console.log('User:', user);
  console.log('Posts:', posts);
  console.log('Comments:', comments);
});

// Все три запроса идут параллельно
// Время: max(fetch времени), а не сумма

Пример 2: React компонент с несколькими API запросами

function useUserData(userId) {
  const [data, setData] = React.useState(null);
  const [loading, setLoading] = React.useState(true);
  const [error, setError] = React.useState(null);

  React.useEffect(() => {
    Promise.all([
      fetch(`/api/users/${userId}`).then(r => r.json()),
      fetch(`/api/users/${userId}/posts`).then(r => r.json())
    ])
      .then(([user, posts]) => {
        setData({ user, posts });
      })
      .catch(err => {
        setError(err);
      })
      .finally(() => {
        setLoading(false);
      });
  }, [userId]);

  return { data, loading, error };
}

Пример 3: Обработка смешанных типов (Promise и значения)

Promise.all работает не только с Promise, но и с обычными значениями:

const mixed = Promise.all([
  Promise.resolve(1),
  2, // обычное значение
  Promise.resolve(3),
  'four' // обычное значение
]);

mixed.then(results => {
  console.log(results); // [1, 2, 3, 'four']
});

Альтернативные методы

Promise.allSettled() — хочешь все результаты, даже ошибки

const promises = [
  Promise.resolve('Success'),
  Promise.reject('Error'),
  Promise.resolve('Another Success')
];

Promise.allSettled(promises).then(results => {
  console.log(results);
  // [
  //   { status: 'fulfilled', value: 'Success' },
  //   { status: 'rejected', reason: 'Error' },
  //   { status: 'fulfilled', value: 'Another Success' }
  // ]
});

// Ключевое отличие: ВСЕ результаты возвращаются, в том числе ошибки!

Promise.race() — первый результат выигрывает

const promise1 = new Promise(resolve => setTimeout(() => resolve('First'), 100));
const promise2 = new Promise(resolve => setTimeout(() => resolve('Second'), 500));

Promise.race([promise1, promise2]).then(result => {
  console.log(result); // 'First' — быстрее
});

Promise.any() — первый успешный результат

const promise1 = Promise.reject('Error 1');
const promise2 = Promise.resolve('Success');
const promise3 = Promise.reject('Error 3');

Promise.any([promise1, promise2, promise3]).then(result => {
  console.log(result); // 'Success' — первый успешный
});

Обработка ошибок

Проблема: какой Promise ошибился?

Promise.all([
  fetchUser(1),
  fetchUser(2),
  fetchUser(3)
]).catch(error => {
  console.log(error); // Какой из трёх ошибился? Не знаем!
});

Решение: Используй Promise.allSettled для диагностики:

Promise.allSettled([
  fetchUser(1),
  fetchUser(2),
  fetchUser(3)
]).then(results => {
  results.forEach((result, index) => {
    if (result.status === 'rejected') {
      console.log(`User ${index + 1} fetch failed:`, result.reason);
    }
  });
});

Обработка ошибок с retry логикой

async function fetchWithRetry(url, retries = 3) {
  for (let i = 0; i < retries; i++) {
    try {
      const response = await fetch(url);
      if (!response.ok) throw new Error(`HTTP ${response.status}`);
      return response.json();
    } catch (error) {
      if (i === retries - 1) throw error; // последняя попытка
      await new Promise(resolve => setTimeout(resolve, 1000 * (i + 1)));
    }
  }
}

Promise.all([
  fetchWithRetry('/api/users/1'),
  fetchWithRetry('/api/users/2'),
  fetchWithRetry('/api/users/3')
]).catch(error => {
  console.log('Ошибка даже после повторов:', error);
});

Сложные сценарии

Вложенные Promise.all

// Загрузить пользователей, потом для каждого загрузить посты
const userIds = [1, 2, 3];

Promise.all(userIds.map(id => fetch(`/api/users/${id}`)))
  .then(responses => Promise.all(responses.map(r => r.json())))
  .then(users => {
    return Promise.all(
      users.map(user =>
        Promise.all([
          Promise.resolve(user),
          fetch(`/api/posts?userId=${user.id}`).then(r => r.json())
        ])
      )
    );
  })
  .then(usersWithPosts => {
    console.log(usersWithPosts);
  });

// Или проще через async/await:
async function loadUsersWithPosts() {
  const users = await fetchUsers();
  const userPosts = await Promise.all(
    users.map(user => fetchUserPosts(user.id))
  );
  return users.map((user, i) => ({
    ...user,
    posts: userPosts[i]
  }));
}

Лучшие практики

  1. Используй Promise.all для параллельных операций — это даёт 3x ускорение для 3 операций
  2. Помни про fail-fast — один error = всё падает
  3. Используй allSettled для диагностики — когда нужно узнать, какой Promise ошибился
  4. async/await часто красивее — но понимай, что под капотом это Promise.all
  5. Порядок результатов сохранён — не нужно переставлять результаты
  6. Не забывай об error handling — даже если думаешь, что ошибки не будет

Эквивалент в async/await

// Promise.all
Promise.all([p1, p2, p3]).then(results => { ... });

// Эквивалент в async/await
const results = await Promise.all([p1, p2, p3]);
// ... код

// Это ПАРАЛЛЕЛЬНО!
const r1 = await p1; // последовательно (медленно)
const r2 = await p2;
const r3 = await p3;

Основной урок: Promise.all — это инструмент для параллелизма. Используй его для ускорения, когда операции независимы.

Как работает Promise.all? | PrepBro