Почему Promise.race не подходит для выбора лучшего покупателя в JavaScript
Что проверяют
Различаете ли вы «первый по времени» и «лучший по значению», и знаете ли весь набор комбинаторов промисов. Заодно это проверка на обработку ошибок: Promise.race завершается и на первом отказе тоже.
Как отвечать
Promise.race отдаёт результат того промиса, который завершился раньше всех, независимо от того, выполнен он или отклонён. Критерий у него один, время. Лучшее предложение это критерий по значению, значит нужны все ответы, а потом выбор максимума:
const settled = await Promise.allSettled(buyers.map(askBuyer));
const best = settled
.filter((r) => r.status === "fulfilled")
.map((r) => r.value)
.reduce((a, b) => (a && a.price >= b.price ? a : b), null);allSettled не падает, если один покупатель ответил ошибкой, а all в этом случае отклонится целиком и выбросит уже пришедшие удачные ответы.
Если ждать всех нельзя, race применяется по назначению, как ограничитель времени для каждого запроса по отдельности, а сверху всё равно остаётся allSettled:
const withTimeout = (p, ms) => Promise.race([
p,
new Promise((_, reject) => setTimeout(() => reject(new Error("timeout")), ms)),
]);Стоит добавить два уточнения. Отмена делается отдельно, через AbortController у fetch: сам по себе проигравший промис не прекращает работу. И есть Promise.any, который берёт первый успешный и игнорирует отказы, пока не отвалятся все.
Где спотыкаются
Предлагают Promise.all как универсальную замену. Один упавший запрос отклоняет всю конструкцию, и лучшее предложение теряется вместе с остальными успешными ответами. Для сбора результатов нужен allSettled.
Думают, что race отменяет проигравших. Он лишь перестаёт их слушать: сетевые запросы уходят до конца, таймеры тикают, а отклонение опоздавшего промиса, который больше никто не обрабатывает, всплывает как необработанный отказ.
Путают race и any. С первым единственный быстро упавший покупатель ломает весь выбор, второй такие отказы пропускает и отклоняется только когда провалились все, отдавая AggregateError.