Как реализовать функцию any, которая работает как Promise.any?
Что проверяют
Знание семантики Promise.any и умение собрать комбинатор промисов руками: счётчик незавершённых, накопление ошибок в правильном порядке, корректный терминальный случай. Попутно смотрят, помнит ли кандидат про пустой массив и про разницу с race.
Как отвечать
Контракт Promise.any: резолвится значением первого успешного промиса, а если отклонились все, отклоняется одним AggregateError, в errors которого лежат причины в порядке исходного массива.
function any(iterable) {
return new Promise((resolve, reject) => {
const items = Array.from(iterable);
const errors = new Array(items.length);
let pending = items.length;
if (pending === 0) {
reject(new AggregateError([], "All promises were rejected"));
return;
}
items.forEach((item, i) => {
Promise.resolve(item).then(resolve, (err) => {
errors[i] = err;
pending -= 1;
if (pending === 0) {
reject(new AggregateError(errors, "All promises were rejected"));
}
});
});
});
}Разбор решений:
Promise.resolve(item)нормализует не-промисы и thenable-объекты, чтобыany([1, Promise.reject(e)])вернул1.- Счётчик, а не
Promise.all. СобратьanyчерезPromise.allнельзя:allпадает на первой же ошибке, а нам нужно дождаться, пока отвалятся все. Ждать все отклонения умеетallSettled, но через него ответ приходит только после самого медленного промиса, то есть теряется главное свойствоany(ответить сразу на первом успехе). - Ошибки по индексу, а не
push.pushрасставит причины в порядке отклонения, а не в порядке входа, иerrors[0]перестанет соответствоватьitems[0]. - Повторные
resolve/rejectбезопасны. После первого срабатывания промис зафиксирован, остальные вызовы игнорируются, специальный флаг не нужен. - Пустой вход должен немедленно отклониться
AggregateErrorс пустым списком, ровно как в спецификации. Без явной проверки промис зависнет навсегда.
Чем отличается от соседей: race завершается первым любым исходом, включая отклонение; all требует успеха от всех; allSettled не отклоняется никогда. Нативный Promise.any появился в ES2021 (Node 15, все актуальные браузеры), так что в проде свою реализацию писать незачем, но на собеседовании просят именно её.
Где спотыкаются
Копят ошибки через push и получают перемешанный порядок в AggregateError, а также забывают про пустой массив: промис не резолвится и не отклоняется, тест просто виснет по таймауту.