Hack Frontend Community

Promise.all, Promise.race, Promise.allSettled, Promise.any

Статические методы Promise

JavaScript предоставляет четыре основных статических метода для работы с несколькими промисами одновременно:

  • Promise.all()
  • Promise.race()
  • Promise.allSettled()
  • Promise.any()

Каждый из них решает разные задачи. Разберём подробно.


Promise.all()

Ожидает выполнения всех промисов. Если хотя бы один отклонён — весь результат отклоняется.

Синтаксис

Promise.all(iterable)

Поведение

  • ✅ Возвращает массив результатов, если все промисы выполнены успешно
  • ❌ Отклоняется с ошибкой первого отклонённого промиса
  • Порядок результатов соответствует порядку промисов

Пример успешного выполнения

const promise1 = Promise.resolve(10);
const promise2 = Promise.resolve(20);
const promise3 = Promise.resolve(30);

Promise.all([promise1, promise2, promise3])
  .then(results => {
    console.log(results);  // [10, 20, 30]
  });

Пример с отклонением

const promise1 = Promise.resolve(10);
const promise2 = Promise.reject('Error!');
const promise3 = Promise.resolve(30);

Promise.all([promise1, promise2, promise3])
  .then(results => {
    console.log(results);
  })
  .catch(error => {
    console.log(error);  // "Error!"
    // promise3 будет проигнорирован
  });

Когда использовать?

  • Загрузка нескольких независимых ресурсов одновременно
  • Все запросы обязательны для продолжения работы
  • Нужно дождаться выполнения всех операций
async function loadUserData(userId) {
  const [user, posts, comments] = await Promise.all([
    fetchUser(userId),
    fetchPosts(userId),
    fetchComments(userId)
  ]);
  
  return { user, posts, comments };
}

Важно:

Если один промис отклоняется, остальные промисы продолжают выполняться, но их результаты будут проигнорированы.


Promise.race()

Возвращает результат первого завершившегося промиса (успешно или с ошибкой).

Синтаксис

Promise.race(iterable)

Поведение

  • Завершается, как только любой промис завершится
  • Возвращает результат или ошибку первого завершённого промиса
  • Остальные промисы игнорируются

Пример

const slow = new Promise(resolve => {
  setTimeout(() => resolve('Медленный'), 2000);
});

const fast = new Promise(resolve => {
  setTimeout(() => resolve('Быстрый'), 500);
});

Promise.race([slow, fast])
  .then(result => {
    console.log(result);  // "Быстрый"
  });

Пример с ошибкой

const success = new Promise(resolve => {
  setTimeout(() => resolve('Успех'), 2000);
});

const failure = new Promise((resolve, reject) => {
  setTimeout(() => reject('Ошибка'), 500);
});

Promise.race([success, failure])
  .then(result => console.log(result))
  .catch(error => console.log(error));  // "Ошибка"

Когда использовать?

  • Таймауты: отменить операцию, если она занимает слишком много времени
  • Запасные варианты: попробовать несколько источников данных
  • Гонка запросов: использовать самый быстрый ответ
// Таймаут для запроса
function fetchWithTimeout(url, timeout = 5000) {
  return Promise.race([
    fetch(url),
    new Promise((_, reject) => 
      setTimeout(() => reject(new Error('Timeout')), timeout)
    )
  ]);
}

fetchWithTimeout('/api/data', 3000)
  .then(response => response.json())
  .catch(error => console.error(error));

Promise.allSettled()

Ожидает завершения всех промисов (успешно или с ошибкой) и возвращает их статусы.

Синтаксис

Promise.allSettled(iterable)

Поведение

  • ✅ Всегда разрешается (никогда не отклоняется)
  • Возвращает массив объектов с результатами каждого промиса
  • Каждый объект содержит status и value / reason

Формат результата

[
  { status: 'fulfilled', value: результат },
  { status: 'rejected', reason: ошибка }
]

Пример

const promises = [
  Promise.resolve(10),
  Promise.reject('Error'),
  Promise.resolve(30)
];

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

Обработка результатов

const results = await Promise.allSettled([
  fetch('/api/users'),
  fetch('/api/posts'),
  fetch('/api/comments')
]);

const successful = results
  .filter(r => r.status === 'fulfilled')
  .map(r => r.value);

const failed = results
  .filter(r => r.status === 'rejected')
  .map(r => r.reason);

console.log('Успешно:', successful.length);
console.log('Неудачно:', failed.length);

Когда использовать?

  • Нужны результаты всех операций, даже если некоторые провалились
  • Агрегация данных из нескольких источников
  • Частичная загрузка данных (показать то, что загрузилось)
async function loadDashboard() {
  const [userResult, statsResult, notificationsResult] = await Promise.allSettled([
    fetchUser(),
    fetchStats(),
    fetchNotifications()
  ]);
  
  return {
    user: userResult.status === 'fulfilled' ? userResult.value : null,
    stats: statsResult.status === 'fulfilled' ? statsResult.value : null,
    notifications: notificationsResult.status === 'fulfilled' 
      ? notificationsResult.value 
      : []
  };
}

ES2020:

Promise.allSettled() был добавлен в ES2020 и поддерживается всеми современными браузерами.


Promise.any()

Возвращает первый успешно выполненный промис. Отклоняется, только если все промисы отклонены.

Синтаксис

Promise.any(iterable)

Поведение

  • ✅ Разрешается с результатом первого успешного промиса
  • ❌ Отклоняется только если все промисы отклонены (с AggregateError)
  • Игнорирует отклонённые промисы, пока есть хотя бы один успешный

Пример

const promises = [
  Promise.reject('Ошибка 1'),
  Promise.resolve('Успех!'),
  Promise.reject('Ошибка 2')
];

Promise.any(promises)
  .then(result => {
    console.log(result);  // "Успех!"
  });

Пример с полным отклонением

const promises = [
  Promise.reject('Ошибка 1'),
  Promise.reject('Ошибка 2'),
  Promise.reject('Ошибка 3')
];

Promise.any(promises)
  .catch(error => {
    console.log(error);  // AggregateError: All promises were rejected
    console.log(error.errors);  // ['Ошибка 1', 'Ошибка 2', 'Ошибка 3']
  });

Когда использовать?

  • Запасные варианты: попробовать несколько источников данных
  • Нужен хотя бы один успешный результат
  • Работа с ненадёжными API (пробуем несколько серверов)
// Загрузка изображения с резервных серверов
async function loadImage(imageName) {
  const servers = [
    `https://cdn1.example.com/${imageName}`,
    `https://cdn2.example.com/${imageName}`,
    `https://cdn3.example.com/${imageName}`
  ];
  
  try {
    const imageUrl = await Promise.any(
      servers.map(url => fetch(url).then(r => {
        if (!r.ok) throw new Error('Failed');
        return url;
      }))
    );
    return imageUrl;
  } catch (error) {
    console.error('Все серверы недоступны');
  }
}

ES2021:

Promise.any() был добавлен в ES2021.


Сравнительная таблица

МетодЗавершается когдаРезультат успехаРезультат ошибки
Promise.allВсе выполнены ИЛИ первый отклонёнМассив всех результатовПервая ошибка
Promise.raceПервый завершилсяРезультат первогоОшибка первого
Promise.allSettledВсе завершилисьМассив {status, value/reason}Никогда не отклоняется
Promise.anyПервый успешный ИЛИ все отклоненыПервый успешный результатAggregateError

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

Загрузка с таймаутом и retry

async function fetchWithRetry(url, retries = 3, timeout = 5000) {
  for (let i = 0; i < retries; i++) {
    try {
      const result = await Promise.race([
        fetch(url),
        new Promise((_, reject) => 
          setTimeout(() => reject(new Error('Timeout')), timeout)
        )
      ]);
      return result;
    } catch (error) {
      if (i === retries - 1) throw error;
      console.log(`Попытка ${i + 1} не удалась, повторяем...`);
    }
  }
}

Параллельная загрузка с ограничением

async function fetchWithLimit(urls, limit = 3) {
  const results = [];
  const executing = [];
  
  for (const url of urls) {
    const promise = fetch(url).then(r => r.json());
    results.push(promise);
    
    if (limit <= urls.length) {
      const executing_promise = promise.then(() => 
        executing.splice(executing.indexOf(executing_promise), 1)
      );
      executing.push(executing_promise);
      
      if (executing.length >= limit) {
        await Promise.race(executing);
      }
    }
  }
  
  return Promise.all(results);
}

Частичная загрузка данных

async function loadPageData() {
  const results = await Promise.allSettled([
    fetchCriticalData(),    // Обязательные данные
    fetchOptionalWidget1(), // Необязательные виджеты
    fetchOptionalWidget2(),
    fetchOptionalWidget3()
  ]);
  
  // Если критические данные не загрузились - показываем ошибку
  if (results[0].status === 'rejected') {
    throw new Error('Не удалось загрузить страницу');
  }
  
  return {
    critical: results[0].value,
    widgets: results.slice(1)
      .filter(r => r.status === 'fulfilled')
      .map(r => r.value)
  };
}

Вывод

  • Promise.all() — все или ничего (параллельная загрузка обязательных данных)
  • Promise.race() — побеждает первый (таймауты, гонка запросов)
  • Promise.allSettled() — результат каждого (частичная загрузка, агрегация)
  • Promise.any() — первый успешный (резервные серверы, fallback)

На собеседовании:

Частые вопросы:

  • Чем отличается Promise.all от Promise.allSettled?
  • Когда использовать Promise.race vs Promise.any?
  • Что произойдёт, если передать пустой массив в каждый метод?
  • Как обработать частичные ошибки при загрузке данных?