Как создать промис, который возвращает результат первого успешно завершившегося промиса в JavaScript
Что проверяют
Знаете ли вы разницу между комбинаторами промисов и умеете ли написать такой комбинатор руками. Готовый ответ это Promise.any, а следом почти всегда просят реализовать его самостоятельно и спрашивают, что будет, если упадут все промисы.
Как отвечать
Начните с разграничения четырёх методов:
Promise.all: ждёт все, отклоняется на первой ошибке;Promise.allSettled: ждёт все и отдаёт массив статусов, сам никогда не отклоняется;Promise.race: берёт первый завершившийся, неважно, успехом или ошибкой;Promise.any: берёт первый успешный. Если отклонились все, возвращаетAggregateError, в полеerrorsкоторого причины в порядке входных промисов.
Promise.any появился в ES2021 и есть во всех актуальных браузерах и Node.js. Ручная реализация:
function any(items) {
return new Promise((resolve, reject) => {
const list = [...items];
const errors = [];
let rejected = 0;
if (list.length === 0) return reject(new AggregateError([]));
list.forEach((p, i) => {
Promise.resolve(p).then(resolve, (err) => {
errors[i] = err;
if (++rejected === list.length) reject(new AggregateError(errors));
});
});
});
}Ключевые детали: Promise.resolve(p) принимает и обычные значения, а повторный вызов resolve игнорируется, поэтому гонку между успешными разруливать не нужно. Остальные промисы продолжают выполняться: если это fetch, их стоит отменить через общий AbortController.
Где спотыкаются
Отвечают Promise.race. Он вернёт первую ошибку, если она пришла раньше успеха, а это прямо противоположно условию задачи.
Считают отказы через errors.length. После записи в errors[2] длина массива уже 3, хотя упал один промис, поэтому нужен отдельный счётчик.
Забывают пустой вход. Без проверки такой промис никогда не завершится, а стандартный Promise.any([]) сразу отклоняется с AggregateError.