Skip to main content

Promise.any() у JavaScript

Promise.any(iterable) приймає масив (або будь-який ітерований об'єкт) промісів і повертає новий проміс, який виконується (fulfilled) щойно перший проміс завершився успішно (resolve). Відхиляється він лише тоді, коли відхилені всі проміси, тобто логіка звучить так: «дай мені перший успішний результат, а помилки ігноруй».

Теорія

TL;DR

  • Promise.any(iterable) віддає значення першого успішного проміса.
  • Проміжні відхилення ігноруються, вони не зупиняють очікування.
  • Якщо відхилились усі, повернений проміс падає з AggregateError.
  • err.errors містить масив усіх причин відхилення.
  • Не проміси загортаються у Promise.resolve(), тому звичайне значення перемагає одразу.
  • Класичний сценарій: fallback до кількох джерел даних або надлишкові запити до API.

Швидкий приклад

javascript
const p1 = Promise.reject('Помилка 1'); const p2 = new Promise(resolve => setTimeout(() => resolve('Успіх 2'), 1000)); const p3 = new Promise(resolve => setTimeout(() => resolve('Успіх 3'), 2000)); Promise.any([p1, p2, p3]) .then(result => console.log('Результат:', result)) .catch(err => console.error('Помилка:', err));

Вивід через 1 секунду:

javascript
Результат: Успіх 2

Перший успішний (p2) «перемагає», навіть якщо решта ще виконується або впаде пізніше.

Синтаксис:

javascript
Promise.any(iterable)

Повертає новий Promise, який завершиться значенням першого fulfilled проміса або відхилиться спеціальною помилкою AggregateError, якщо всі проміси впали.

Як це працює під капотом

  1. Усі проміси запускаються паралельно, точніше кожен уже виконується з моменту створення.
  2. Якщо хоча б один із них виконався успішно (resolve), Promise.any() негайно завершується з цим результатом.
  3. Якщо всі виявились rejected, повертається помилка типу AggregateError з масивом усіх причин.

Тобто відхилення не завершує очікування: лічильник помилок просто зростає, і тільки коли він зрівняється з кількістю промісів, спрацює catch.

Якщо всі проміси відхиляються

javascript
const p1 = Promise.reject('Помилка A'); const p2 = Promise.reject('Помилка B'); Promise.any([p1, p2]) .then(console.log) .catch(err => console.error(err));

Вивід:

javascript
AggregateError: All promises were rejected

Помилка містить список усіх причин:

javascript
err.errors // ['Помилка A', 'Помилка B']

Детальніше про поведінку AggregateError:

javascript
Promise.any([ Promise.reject('Помилка 1'), Promise.reject('Помилка 2') ]).catch(err => { console.error(err.name); // AggregateError console.error(err.errors); // ['Помилка 1', 'Помилка 2'] });

Порядок в err.errors відповідає порядку вхідних промісів, а не порядку, у якому вони відхилились.

Комбінація успіхів і помилок

javascript
const slow = new Promise(resolve => setTimeout(() => resolve('Повільний успіх'), 2000)); const fail = Promise.reject('Помилка!'); const fast = new Promise(resolve => setTimeout(() => resolve('Швидкий успіх'), 500)); Promise.any([slow, fail, fast]) .then(console.log) .catch(console.error);

Результат через 0.5 секунди:

javascript
Швидкий успіх

Навіть якщо fail упав раніше за всіх, Promise.any() чекає на перший успішний проміс.

Fallback до кількох джерел

Уявіть, що у вас три сервери, і треба отримати дані з першого, який відповість успішно, навіть якщо два інші недоступні:

javascript
const fetchFromA = fetch('/api/a'); // може впасти const fetchFromB = fetch('/api/b'); const fetchFromC = fetch('/api/c'); Promise.any([fetchFromA, fetchFromB, fetchFromC]) .then(res => console.log('Відповідь отримано')) .catch(() => console.error('Усі джерела недоступні'));

Так реалізується стратегія fallback: успіх будь-якого джерела вважається достатнім.

Відмінність від інших методів

МетодЩо повертаєЗавершується колиПомилка при
Promise.all()Масив результатів усіх успішнихУсі проміси fulfilledПерший reject
Promise.allSettled()Масив статусів (fulfilled / rejected)Усі проміси завершеніНіколи
Promise.race()Перший завершений проміс (успіх або помилка)Будь-який resolve чи rejectЗавершується на першому reject
Promise.any()Перший успішний результатПерший fulfilledКоли всі rejected

Тобто:

  • Promise.race() реагує на перший завершений проміс, включно з помилками;
  • Promise.any() реагує лише на перший успішний.

Проста аналогія:

Уявіть, що ви відправили трьох кур'єрів різними маршрутами.

  • Promise.any() каже: «чий результат першим успішно дійде, той я і прийму, а помилки решти мене не цікавлять».
  • І лише якщо всі загубляться, ви дізнаєтесь про помилку.

Підсумок:

ВластивістьЗначення
Що робитьПовертає перший успішно виконаний проміс
Якщо всі відхиленіПовертає AggregateError з причинами
Тип поверненого значенняPromise
Схожий наPromise.race(), але ігнорує reject
Типові сценаріїfallback запити, резервні джерела даних, надлишкові запити до API

Типові помилки

  • Очікувати звичайну помилку в catch. Приходить AggregateError, а конкретні причини лежать у err.errors, тому err.message сам по собі майже нічого не пояснює.
  • Плутати з Promise.race(). race() впаде на першому ж відхиленні, any() же продовжить чекати на успіх.
  • Забувати про підтримку. Promise.any() це ES2021: у дуже старих середовищах потрібен поліфіл.
  • Думати, що програні запити скасовуються. Решта промісів виконується далі і навантажує мережу, для справжнього скасування потрібен AbortController.
  • Передавати порожній масив. Promise.any([]) одразу відхиляється з AggregateError, бо успішному промісу нема звідки взятися.
  • Вважати, що fetch відхиляється на помилці сервера. Відповідь зі статусом 500 для fetch успішна, тому в fallback сценарії статус треба перевіряти вручну і кидати помилку самому.

Коротка відповідь

Для співбесіди
Premium

Коротка відповідь допоможе вам впевнено відповідати на цю тему під час співбесіди.