Skip to main content

Promise.race() у JavaScript

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

Теорія

TL;DR

  • Promise.race(iterable) повертає проміс, що повторює долю першого завершеного проміса.
  • Результат може бути як успіхом, так і помилкою: усе залежить від того, хто фінішував першим.
  • Решта промісів ігноруються, але не скасовуються, вони продовжують виконуватись у фоні.
  • Звичайні значення автоматично загортаються в Promise.resolve(), тому не проміс перемагає миттєво.
  • Головний практичний сценарій: таймаут для операції, що може зависнути.

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

javascript
const fast = new Promise(resolve => setTimeout(() => resolve('Швидкий'), 1000)); const slow = new Promise(resolve => setTimeout(() => resolve('Повільний'), 3000)); Promise.race([fast, slow]).then(console.log);

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

javascript
Швидкий

Другий проміс (slow) продовжить виконуватись у фоні, але результат уже визначений: «гонку» виграв fast.

Синтаксис:

javascript
Promise.race(iterable)

Повертає новий Promise, який завершиться значенням першого успішно виконаного проміса (resolve) або впаде з помилкою, якщо першим завершиться reject.

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

  1. Усі проміси починають виконуватись одночасно (практично одночасно, бо кожен стартував у момент створення).
  2. Щойно один із них завершився (resolve або reject):
    • результат цього проміса стає результатом усієї гонки;
    • Promise.race() переходить у стан fulfilled або rejected;
    • решта промісів ігноруються (але не скасовуються).
  3. Решта промісів продовжують виконуватись у фоні, проте їхній результат ні на що не впливає, адже проміс змінює стан лише один раз.

Якщо перший проміс завершився помилкою

javascript
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 секунду:

javascript
Помилка!

Навіть попри те, що ok згодом успішно виконається, результат усієї гонки вже вирішений помилкою. Саме цим Promise.race() відрізняється від Promise.any(), який ігнорує відхилення і чекає на перший успішний проміс.

Таймаут на базі Promise.race()

Поширена практика: використовувати Promise.race(), щоб поставити обмеження за часом.

javascript
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 секунди:

javascript
Перевищено час очікування (2000 мс)

Це один із найчастіших сценаріїв застосування Promise.race(): обмеження операцій, що «зависають», наприклад мережевих запитів.

Миттєві проміси та не проміси

Якщо всі проміси завершуються миттєво, перемагає перший у порядку ітерації:

javascript
Promise.race([ Promise.resolve('Перший'), Promise.resolve('Другий'), Promise.resolve('Третій') ]).then(console.log); // Перший

Якщо передати не проміс, Promise.race() автоматично «загортає» звичайне значення у Promise.resolve(), тож воно виграє гонку одразу:

javascript
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.

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

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

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