Почему нельзя просто вернуть Promise.resolve с промисом внутри в реализации any?
Что проверяют
Понимаете ли вы семантику Promise.any и что делает Promise.resolve, когда внутри уже промис. Задача маленькая, но по ней сразу видно, читали ли вы спецификацию или помните промисы по примерам с then.
Как отвечать
Promise.resolve(p), где p уже нативный промис, возвращает ровно его же, новый объект не создаётся. Значит результат будет вести себя по правилам этого промиса: первый же его отказ станет отказом всего вызова. А any устроен наоборот: он ждёт первое успешное выполнение, а отказы копит и молчит, пока не откажут все, и только тогда отклоняется с AggregateError, у которого в поле errors лежат все причины.
Делегировать одному промису поэтому нельзя: нужна подписка сразу на все, счётчик оставшихся и сбор ошибок по индексам входа.
function any(items) {
return new Promise((resolve, reject) => {
const list = [...items];
const errors = new Array(list.length);
let left = list.length;
if (left === 0) reject(new AggregateError(errors));
list.forEach((item, i) =>
Promise.resolve(item).then(resolve, (e) => {
errors[i] = e;
if (--left === 0) reject(new AggregateError(errors));
}));
});
}В конечное состояние промис переходит один раз, поэтому многократный вызов resolve безопасен: выигрывает первый.
Где спотыкаются
Пишут семантику all или race и отклоняются на первой же ошибке. Тогда один упавший запрос из пяти хоронит весь вызов, хотя смысл any ровно в обратном.
Второе: забывают пустой вход и обычные значения в списке. Пустой итератор обязан отклониться сразу, а число или строка среди элементов должны победить немедленно, ради чего каждый элемент и заворачивают в Promise.resolve.
Третье: складывают ошибки через push и проверяют завершение по длине массива. Порядок в errors тогда не совпадает со входом, а счётчик надёжнее держать отдельным числом: сравнение длин ломается на разреженном массиве.