Learn JavaScript

Promise.any() - ожидание первого успешного промиса в JavaScript

Promise.any() нужен, когда из нескольких асинхронных операций достаточно получить первый успешный результат. Метод вызывается у Promise, принимает набор значений и возвращает новый промис, который завершится успешно, как только хотя бы один элемент завершится успешно.

Главное отличие от Promise.race() в том, что ошибки не выигрывают эту гонку. Promise.any() игнорирует отклонённые элементы, пока остаётся шанс получить хотя бы один успешный результат.

Синтаксис

Базовая запись выглядит так:

Promise.any(iterable)

Где:

  • iterable - итерируемый набор значений, чаще всего массив

Обычно в Promise.any() передают массив промисов, но внутри могут быть и обычные значения.

Что возвращает Promise.any()

Метод всегда возвращает объект Promise.

Если хотя бы один элемент завершится успешно, итоговый промис станет fulfilled и вернёт значение этого элемента.

const promise = Promise.any([
  new Promise((resolve) => setTimeout(() => resolve("slow"), 50)),
  new Promise((resolve) => setTimeout(() => resolve("fast"), 10)),
]);

console.log(promise instanceof Promise); // true

promise.then((value) => {
  console.log(value); // "fast"
});

Если же успешно не завершится ни один элемент, итоговый промис станет rejected.

Простой пример

const cachePromise = Promise.reject(new Error("Кэш пуст"));

const apiPromise = new Promise((resolve) => {
  setTimeout(() => resolve("Данные с сервера"), 50);
});

Promise.any([cachePromise, apiPromise]).then((value) => {
  console.log(value); // "Данные с сервера"
});

Такой подход удобен, когда есть несколько возможных источников данных и достаточно первого успешного ответа.

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

Это главное поведение метода.

Promise.any() не завершается на первой ошибке. Он продолжает ждать, пока не получит первый fulfilled.

const fastReject = new Promise((_, reject) => {
  setTimeout(() => reject(new Error("boom")), 10);
});

const slowSuccess = new Promise((resolve) => {
  setTimeout(() => resolve("ok"), 50);
});

Promise.any([fastReject, slowSuccess]).then((value) => {
  console.log(value); // "ok"
});

Хотя ошибка произошла раньше, итог всё равно успешный, потому что позже нашёлся хотя бы один успешный результат.

Первая ошибка не делает итоговый промис отклонённым

Это особенно важно не путать с Promise.race().

const first = Promise.reject("Ошибка 1");
const second = new Promise((resolve) => {
  setTimeout(() => resolve("Успех"), 20);
});

Promise.any([first, second])
  .then((value) => {
    console.log(value); // "Успех"
  })
  .catch((error) => {
    console.log(error);
  });

Здесь catch() не выполнится, потому что для Promise.any() важен хотя бы один успех.

Если все элементы завершились с ошибкой, будет AggregateError

Это ключевой нюанс Promise.any().

Когда успешного результата нет вообще, итоговый промис завершается с ошибкой AggregateError.

Promise.any([
  Promise.reject("a"),
  Promise.reject("b"),
]).catch((error) => {
  console.log(error.name); // "AggregateError"
  console.log(error.errors); // ["a", "b"]
});

У такого объекта есть свойство errors, в котором хранится массив причин отказа.

error.errors сохраняет порядок входных элементов

Это полезно помнить при разборе ошибок.

Причины в AggregateError.errors идут в порядке входного массива, а не по скорости, с которой промисы завершились ошибкой.

const first = new Promise((_, reject) => {
  setTimeout(() => reject("first"), 50);
});

const second = new Promise((_, reject) => {
  setTimeout(() => reject("second"), 10);
});

Promise.any([first, second]).catch((error) => {
  console.log(error.errors); // ["first", "second"]
});

Хотя второй промис завершился раньше, в массиве ошибок он всё равно остаётся на второй позиции.

Обычные значения тоже работают

Это ещё один полезный нюанс.

В Promise.any() можно передавать не только промисы, но и обычные значения. Они считаются уже успешно готовыми.

console.log("start");

Promise.any([42]).then((value) => {
  console.log(value); // 42
});

console.log("end");

Результат будет таким:

start
end
42

Даже если внутри уже готовое значение, обработчик then() всё равно выполнится асинхронно.

Пустой массив даёт AggregateError

Это важная особенность метода.

Если передать пустой массив, успешного результата взять неоткуда, поэтому итоговый промис сразу станет rejected с ошибкой AggregateError.

console.log("start");

Promise.any([]).catch((error) => {
  console.log(error.name); // "AggregateError"
  console.log(error.errors.length); // 0
});

console.log("end");

Результат будет таким:

start
end
AggregateError
0

То есть catch() всё равно сработает асинхронно.

Остальные операции не отменяются автоматически

Это важный практический нюанс.

Когда Promise.any() уже получил первый успешный результат, остальные запущенные операции не останавливаются сами по себе.

const slow = new Promise((resolve) => {
  setTimeout(() => {
    console.log("slow done");
    resolve("slow");
  }, 50);
});

const fast = new Promise((resolve) => {
  setTimeout(() => resolve("fast"), 10);
});

Promise.any([slow, fast]).then((value) => {
  console.log("winner:", value); // "winner: fast"
});

Итог уже определится по fast, но код внутри slow всё равно продолжит выполняться.

Чем Promise.any() отличается от Promise.race()

Это сравнение особенно полезно запомнить.

  • Promise.any() ждёт первый успешный результат
  • Promise.race() завершается на первом результате вообще, включая ошибку
const fastReject = new Promise((_, reject) => {
  setTimeout(() => reject(new Error("boom")), 10);
});

const slowSuccess = new Promise((resolve) => {
  setTimeout(() => resolve("ok"), 50);
});

Promise.race([fastReject, slowSuccess]).catch((error) => {
  console.log("race:", error.message); // "race: boom"
});

Promise.any([fastReject, slowSuccess]).then((value) => {
  console.log("any:", value); // "any: ok"
});

Если нужен именно первый успешный ответ, чаще всего правильнее использовать Promise.any().

Когда это полезно

Promise.any() особенно удобен, когда:

  • есть несколько источников данных и достаточно первого успешного ответа
  • часть запросов может завершаться ошибкой, но это не должно ломать общий сценарий
  • вы хотите выбрать первый доступный сервер, зеркало или кэш
  • нужен резервный вариант, если основной источник не ответил успешно

Пример:

Promise.any([
  fetch("/api/mirror-1").then((response) => response.json()),
  fetch("/api/mirror-2").then((response) => response.json()),
  fetch("/api/mirror-3").then((response) => response.json()),
])
  .then((data) => {
    console.log("Первый успешный источник:", data);
  })
  .catch((error) => {
    console.log("Все источники завершились ошибкой:", error.errors);
  });

Такой код удобен, если достаточно одного рабочего источника.

Частые ошибки

Думать, что Promise.any() падает на первой ошибке

Promise.any([
  Promise.reject("fail"),
  Promise.resolve("ok"),
]).then((value) => {
  console.log(value); // "ok"
});

Обычная ошибка одного элемента не ломает весь Promise.any(), пока остаётся шанс на успех.

Путать Promise.any() с Promise.race()

const fastReject = new Promise((_, reject) => {
  setTimeout(() => reject(new Error("boom")), 10);
});

const slowSuccess = new Promise((resolve) => {
  setTimeout(() => resolve("ok"), 50);
});

Promise.any([fastReject, slowSuccess]).then((value) => {
  console.log(value); // "ok"
});

Здесь выиграет не первая ошибка, а первый успешный результат.

Ожидать массив успешных значений

Promise.any([
  Promise.resolve("A"),
  Promise.resolve("B"),
]).then((value) => {
  console.log(value); // "A"
});

Promise.any() возвращает одно итоговое значение, а не массив.

Не разбирать AggregateError

Promise.any([
  Promise.reject("a"),
  Promise.reject("b"),
]).catch((error) => {
  console.log(error.errors); // ["a", "b"]
});

Если все операции завершились ошибкой, полезно смотреть именно в error.errors.

Передавать неитерируемое значение

Promise.any(123).catch((error) => {
  console.log(error.name); // "TypeError"
});

Метод ожидает массив или другой итерируемый объект.

Быстрая памятка

  • Promise.any() вызывается у Promise
  • метод всегда возвращает объект Promise
  • итоговым значением становится первый успешный результат
  • ошибки отдельных элементов игнорируются, пока не найден успех
  • если все элементы завершились с ошибкой, будет AggregateError
  • причины отказов доступны в error.errors
  • порядок error.errors совпадает с порядком входных элементов
  • обычные значения внутри массива допустимы
  • пустой массив даёт AggregateError
  • Promise.any() не отменяет остальные уже запущенные операции автоматически

Коротко

Promise.any() в JavaScript нужен, когда из нескольких операций достаточно получить первый успешный результат. Главное, что стоит запомнить: метод игнорирует отдельные ошибки, не путается с первой неудачей, возвращает одно успешное значение, а если успеха нет вообще, завершает работу с AggregateError, где причины ошибок лежат в error.errors.