Promise.race() нужен, когда важен не итог всех операций, а самый первый завершившийся результат. Метод вызывается у Promise, принимает набор значений и возвращает новый промис, который завершится сразу после того, как завершится первый элемент: либо успешно, либо с ошибкой.
Синтаксис
Базовая запись выглядит так:
Promise.race(iterable)
Где:
iterable- итерируемый набор значений, чаще всего массив
Обычно в Promise.race() передают массив промисов, но внутри могут быть и обычные значения.
Что возвращает Promise.race()
Метод всегда возвращает объект Promise.
Итоговый промис завершится тогда, когда завершится первый элемент из переданного набора.
const promise = Promise.race([
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"
});
Здесь итоговый промис получает значение "fast", потому что второй элемент завершился раньше.
Простой пример
const first = new Promise((resolve) => {
setTimeout(() => resolve("Первый ответ"), 100);
});
const second = new Promise((resolve) => {
setTimeout(() => resolve("Второй ответ"), 50);
});
Promise.race([first, second]).then((value) => {
console.log(value); // "Второй ответ"
});
Это удобно, когда нужен самый быстрый результат, а не все результаты сразу.
Promise.race() завершается по первому результату
Это главное поведение метода.
Promise.race() не ждёт, пока завершатся все элементы. Он заканчивает работу сразу, как только первый элемент перейдёт в состояние fulfilled или rejected.
const slow = new Promise((resolve) => {
setTimeout(() => resolve("slow"), 100);
});
const fast = new Promise((resolve) => {
setTimeout(() => resolve("fast"), 10);
});
Promise.race([slow, fast]).then((value) => {
console.log(value); // "fast"
});
Здесь неважно, что slow тоже когда-то завершится. Победителем становится тот, кто завершился первым.
Первый успешный результат делает итоговый промис успешным
Если первым завершился промис со значением, итоговый Promise.race() станет fulfilled.
const fastSuccess = new Promise((resolve) => {
setTimeout(() => resolve("ok"), 10);
});
const slowSuccess = new Promise((resolve) => {
setTimeout(() => resolve("later"), 50);
});
Promise.race([fastSuccess, slowSuccess]).then((value) => {
console.log(value); // "ok"
});
Результатом будет одно значение, а не массив значений.
Первая ошибка делает итоговый промис отклонённым
Это важный момент, который часто путают с Promise.any().
Если раньше всех завершится промис с ошибкой, итоговый Promise.race() сразу станет rejected.
const slowSuccess = new Promise((resolve) => {
setTimeout(() => resolve("done"), 50);
});
const fastReject = new Promise((_, reject) => {
setTimeout(() => reject(new Error("boom")), 10);
});
Promise.race([slowSuccess, fastReject])
.then((value) => {
console.log(value);
})
.catch((error) => {
console.log(error.message); // "boom"
});
То есть Promise.race() реагирует на первое завершение вообще, а не только на первый успех.
Обычные значения тоже участвуют в гонке
Это ещё один полезный нюанс.
В Promise.race() можно передавать не только промисы, но и обычные значения. Они рассматриваются как уже успешно готовые.
console.log("start");
Promise.race([42]).then((value) => {
console.log(value); // 42
});
console.log("end");
Результат будет таким:
start
end
42
Даже если внутри уже готовое значение, обработчик then() всё равно выполнится асинхронно.
Пустой массив оставляет промис в состоянии pending
Это очень важная особенность Promise.race().
Если передать пустой массив, у метода просто не будет участника, который мог бы завершиться первым. Поэтому итоговый промис так и останется в состоянии pending.
const promise = Promise.race([]);
promise.then(() => {
console.log("done");
});
setTimeout(() => {
console.log("still pending"); // "still pending"
}, 100);
Здесь then() не выполнится, потому что гонка так и не начнётся по-настоящему.
Остальные операции не отменяются автоматически
Это один из самых важных практических нюансов.
Promise.race() выбирает первый завершившийся результат, но не останавливает остальные уже запущенные операции.
const slow = new Promise((resolve) => {
setTimeout(() => {
console.log("slow done");
resolve("slow");
}, 50);
});
const fast = new Promise((resolve) => {
setTimeout(() => resolve("fast"), 10);
});
Promise.race([slow, fast]).then((value) => {
console.log("winner:", value); // "winner: fast"
});
Сначала вы получите результат победителя, но код внутри slow всё равно продолжит выполняться.
Это особенно важно помнить при запросах, таймерах и других побочных эффектах.
Promise.race() часто используют для таймаута
Это один из самых популярных практических сценариев.
function fetchWithTimeout(url, timeoutMs) {
const timeoutPromise = new Promise((_, reject) => {
setTimeout(() => {
reject(new Error("Время ожидания истекло"));
}, timeoutMs);
});
return Promise.race([fetch(url), timeoutPromise]);
}
fetchWithTimeout("/api/data", 3000)
.then((response) => response.json())
.then((data) => {
console.log(data);
})
.catch((error) => {
console.log(error.message);
});
Такой код перестаёт ждать слишком долгий результат и переходит в catch(), если раньше сработал таймер.
Но есть важный нюанс: сам fetch() при этом не отменяется автоматически. Если нужна именно отмена запроса, обычно используют AbortController.
Чем Promise.race() отличается от Promise.any()
Это сравнение полезно запомнить.
Promise.race()завершается на первом результате вообще: и на успехе, и на ошибкеPromise.any()завершается на первом успешном результате и не падает на первой ошибке
Promise.race([
Promise.reject(new Error("boom")),
Promise.resolve("ok"),
]).catch((error) => {
console.log("race:", error.message); // "race: boom"
});
Если нужен именно первый успешный ответ, Promise.race() подходит не всегда.
Когда это полезно
Promise.race() особенно удобен, когда:
- нужен самый быстрый ответ из нескольких источников
- нужно ограничить ожидание таймаутом
- важен первый доступный результат, а не полный набор результатов
- нужно быстрее продолжить выполнение, как только определится победитель
Пример:
Promise.race([
fetch("/api/mirror-1").then((response) => response.json()),
fetch("/api/mirror-2").then((response) => response.json()),
])
.then((data) => {
console.log("Самый быстрый источник:", data);
})
.catch((error) => {
console.log("Ошибка:", error);
});
Такой подход удобен, если несколько источников могут отдать одинаковые данные, и достаточно первого ответа.
Частые ошибки
Думать, что Promise.race() ждёт первый успех
const slowSuccess = new Promise((resolve) => {
setTimeout(() => resolve("ok"), 50);
});
const fastReject = new Promise((_, reject) => {
setTimeout(() => reject(new Error("boom")), 10);
});
Promise.race([slowSuccess, fastReject]).catch((error) => {
console.log(error.message); // "boom"
});
Метод реагирует на первое завершение, а не на первый успешный результат.
Ожидать массив результатов
Promise.race([
Promise.resolve("A"),
Promise.resolve("B"),
]).then((value) => {
console.log(value); // "A"
});
Promise.race() возвращает одно итоговое значение или одну итоговую ошибку, а не массив.
Думать, что проигравшие операции автоматически останавливаются
const slow = new Promise((resolve) => {
setTimeout(() => {
console.log("slow done");
resolve("slow");
}, 50);
});
const fast = new Promise((resolve) => {
setTimeout(() => resolve("fast"), 10);
});
Promise.race([slow, fast]).then((value) => {
console.log(value); // "fast"
});
После завершения гонки остальные операции могут продолжать выполняться.
Передавать пустой массив и ждать then()
Promise.race([]).then(() => {
console.log("done");
});
Такой обработчик не выполнится, потому что итоговый промис останется в состоянии pending.
Передавать неитерируемое значение
Promise.race(123).catch((error) => {
console.log(error.name); // "TypeError"
});
Метод ожидает массив или другой итерируемый объект.
Быстрая памятка
Promise.race()вызывается уPromise- метод всегда возвращает объект
Promise - итог зависит от первого завершившегося элемента
- первый результат может быть и успешным значением, и ошибкой
- результатом будет одно значение или одна ошибка, а не массив
- обычные значения внутри массива допустимы
- пустой массив оставляет итоговый промис в состоянии
pending Promise.race()не отменяет остальные уже запущенные операции автоматически
Коротко
Promise.race() в JavaScript нужен, когда важен первый завершившийся результат из нескольких операций. Главное, что стоит запомнить: метод завершается на первом fulfilled или rejected, возвращает один итоговый результат, а не массив, умеет работать с обычными значениями, оставляет пустую гонку в состоянии pending и особенно полезен для таймаутов и сценариев, где достаточно самого быстрого ответа.