Promise.any() у JavaScript
Promise.any(iterable) приймає масив (або будь-який ітерований об'єкт) промісів і повертає новий проміс, який виконується (fulfilled) щойно перший проміс завершився успішно (resolve). Відхиляється він лише тоді, коли відхилені всі проміси, тобто логіка звучить так: «дай мені перший успішний результат, а помилки ігноруй».
Теорія
TL;DR
Promise.any(iterable)віддає значення першого успішного проміса.- Проміжні відхилення ігноруються, вони не зупиняють очікування.
- Якщо відхилились усі, повернений проміс падає з
AggregateError. err.errorsмістить масив усіх причин відхилення.- Не проміси загортаються у
Promise.resolve(), тому звичайне значення перемагає одразу. - Класичний сценарій: fallback до кількох джерел даних або надлишкові запити до API.
Швидкий приклад
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 секунду:
Результат: Успіх 2Перший успішний (p2) «перемагає», навіть якщо решта ще виконується або впаде пізніше.
Синтаксис:
Promise.any(iterable)Повертає новий Promise, який завершиться значенням першого fulfilled проміса або відхилиться спеціальною помилкою AggregateError, якщо всі проміси впали.
Як це працює під капотом
- Усі проміси запускаються паралельно, точніше кожен уже виконується з моменту створення.
- Якщо хоча б один із них виконався успішно (
resolve),Promise.any()негайно завершується з цим результатом. - Якщо всі виявились
rejected, повертається помилка типуAggregateErrorз масивом усіх причин.
Тобто відхилення не завершує очікування: лічильник помилок просто зростає, і тільки коли він зрівняється з кількістю промісів, спрацює catch.
Якщо всі проміси відхиляються
const p1 = Promise.reject('Помилка A');
const p2 = Promise.reject('Помилка B');
Promise.any([p1, p2])
.then(console.log)
.catch(err => console.error(err));Вивід:
AggregateError: All promises were rejectedПомилка містить список усіх причин:
err.errors // ['Помилка A', 'Помилка B']Детальніше про поведінку AggregateError:
Promise.any([
Promise.reject('Помилка 1'),
Promise.reject('Помилка 2')
]).catch(err => {
console.error(err.name); // AggregateError
console.error(err.errors); // ['Помилка 1', 'Помилка 2']
});Порядок в err.errors відповідає порядку вхідних промісів, а не порядку, у якому вони відхилились.
Комбінація успіхів і помилок
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 секунди:
Швидкий успіхНавіть якщо fail упав раніше за всіх, Promise.any() чекає на перший успішний проміс.
Fallback до кількох джерел
Уявіть, що у вас три сервери, і треба отримати дані з першого, який відповість успішно, навіть якщо два інші недоступні:
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 сценарії статус треба перевіряти вручну і кидати помилку самому.
Коротка відповідь
Для співбесідиКоротка відповідь допоможе вам впевнено відповідати на цю тему під час співбесіди.