Promise.race() у JavaScript
Promise.race(iterable) приймає масив (або будь-який ітерований об'єкт) промісів і повертає новий проміс, який виконується (fulfilled) або відхиляється (rejected) щойно завершився перший із переданих промісів. Не має значення, успішно чи з помилкою: це «гонка» промісів, у якій хто завершився першим, той і визначає результат.
Теорія
TL;DR
Promise.race(iterable)повертає проміс, що повторює долю першого завершеного проміса.- Результат може бути як успіхом, так і помилкою: усе залежить від того, хто фінішував першим.
- Решта промісів ігноруються, але не скасовуються, вони продовжують виконуватись у фоні.
- Звичайні значення автоматично загортаються в
Promise.resolve(), тому не проміс перемагає миттєво. - Головний практичний сценарій: таймаут для операції, що може зависнути.
Швидкий приклад
const fast = new Promise(resolve => setTimeout(() => resolve('Швидкий'), 1000));
const slow = new Promise(resolve => setTimeout(() => resolve('Повільний'), 3000));
Promise.race([fast, slow]).then(console.log);Результат через 1 секунду:
ШвидкийДругий проміс (slow) продовжить виконуватись у фоні, але результат уже визначений: «гонку» виграв fast.
Синтаксис:
Promise.race(iterable)Повертає новий Promise, який завершиться значенням першого успішно виконаного проміса (resolve) або впаде з помилкою, якщо першим завершиться reject.
Як це працює під капотом
- Усі проміси починають виконуватись одночасно (практично одночасно, бо кожен стартував у момент створення).
- Щойно один із них завершився (
resolveабоreject):- результат цього проміса стає результатом усієї гонки;
Promise.race()переходить у станfulfilledабоrejected;- решта промісів ігноруються (але не скасовуються).
- Решта промісів продовжують виконуватись у фоні, проте їхній результат ні на що не впливає, адже проміс змінює стан лише один раз.
Якщо перший проміс завершився помилкою
const ok = new Promise(resolve => setTimeout(() => resolve('Все гаразд'), 2000));
const fail = new Promise((_, reject) => setTimeout(() => reject('Помилка!'), 1000));
Promise.race([ok, fail])
.then(console.log)
.catch(console.error);Результат через 1 секунду:
Помилка!Навіть попри те, що ok згодом успішно виконається, результат усієї гонки вже вирішений помилкою. Саме цим Promise.race() відрізняється від Promise.any(), який ігнорує відхилення і чекає на перший успішний проміс.
Таймаут на базі Promise.race()
Поширена практика: використовувати Promise.race(), щоб поставити обмеження за часом.
function timeout(ms) {
return new Promise((_, reject) =>
setTimeout(() => reject(`Перевищено час очікування (${ms} мс)`), ms)
);
}
const fetchData = new Promise(resolve =>
setTimeout(() => resolve('Відповідь отримано'), 3000)
);
Promise.race([fetchData, timeout(2000)])
.then(console.log)
.catch(console.error);Вивід через 2 секунди:
Перевищено час очікування (2000 мс)Це один із найчастіших сценаріїв застосування Promise.race(): обмеження операцій, що «зависають», наприклад мережевих запитів.
Миттєві проміси та не проміси
Якщо всі проміси завершуються миттєво, перемагає перший у порядку ітерації:
Promise.race([
Promise.resolve('Перший'),
Promise.resolve('Другий'),
Promise.resolve('Третій')
]).then(console.log); // ПершийЯкщо передати не проміс, Promise.race() автоматично «загортає» звичайне значення у Promise.resolve(), тож воно виграє гонку одразу:
Promise.race([42, Promise.resolve('OK')]).then(console.log); // 42Відмінність від інших методів
| Метод | Що робить | Завершується коли | При помилці |
|---|---|---|---|
Promise.all() | Чекає на всі проміси | Усі завершені успішно | Падає на першому reject |
Promise.allSettled() | Чекає на всіх, незалежно від результату | Усі завершені | Не падає ніколи |
Promise.race() | Бере перший завершений | Перший resolve або reject | Залежить від першого проміса |
Promise.any() | Бере перший успішний (resolve) | Перший fulfilled | Падає, якщо всі rejected |
Проста аналогія:
Уявіть гонку. У вас кілька кур'єрів: чий результат прийде першим, той ви і приймаєте. Решта ще в дорозі, але вам уже байдуже, потрібне ви отримали.
Підсумок по методу:
| Особливість | Promise.race() |
|---|---|
| Приймає | Масив (або інший ітерований об'єкт) промісів |
| Завершується коли | Перший проміс виконається або відхилиться |
| Повертає | Результат (або помилку) першого завершеного проміса |
| Решта промісів | Ігноруються (але не скасовуються) |
| Часте застосування | Обмеження за часом (timeout), конкуренція запитів |
Типові помилки
- Думати, що програні проміси скасовуються. Вони працюють далі: запит летить, таймер цокає, ресурси витрачаються. Для реального скасування потрібен
AbortController. - Плутати
race()зany().race()завершиться помилкою, якщо першим завершився відхилений проміс,any()же чекає на перший успішний. - Забувати
.catch(). Гонка з таймаутом за визначенням може відхилитись, і без обробника ви отримаєтеunhandled promise rejection. - Передавати порожній масив.
Promise.race([])ніколи не завершиться, бо переможцю просто нема звідки взятися. - Лишати таймер після перемоги основної операції.
setTimeoutіз таймауту доживе до кінця і в Node.js триматиме процес живим, тому таймер варто чистити черезclearTimeoutуfinally.
Коротка відповідь
Для співбесідиКоротка відповідь допоможе вам впевнено відповідати на цю тему під час співбесіди.