Перейти к содержимому

Promise.all / allSettled polyfill

Полифиллы Promise-комбинаторов проверяют знание промис-контракта: reject первым же отказом (для .all), обработку пустого массива, порядок результатов независимо от порядка выполнения и разницу семантики .all vs .allSettled. Интервьюер ждёт корректный счётчик и обработку не-промисных значений через Promise.resolve.

Promise.all — reject при первой ошибке, иначе массив результатов в том же порядке:

/**
* Полифилл Promise.all.
* Разрешается массивом значений в порядке входного массива.
* Отклоняется при первом rejected promise (fail-fast).
* @param {Iterable<any>} promises
* @returns {Promise<any[]>}
*/
function promiseAll(promises) {
return new Promise((resolve, reject) => {
const items = [...promises]; // Iterable → Array (поддержка Set, Generator и т.д.)
if (items.length === 0) return resolve([]); // пустой → немедленно []
const results = new Array(items.length);
let remaining = items.length;
items.forEach((item, i) => {
// Promise.resolve оборачивает не-промисы (числа, строки, undefined)
Promise.resolve(item).then(
value => {
results[i] = value; // сохраняем по индексу — порядок гарантирован
if (--remaining === 0) resolve(results);
},
reject, // первый rejected пробрасывается наружу
);
});
});
}
// usage
promiseAll([
Promise.resolve(1),
Promise.resolve(2),
42, // не-промис — обернётся в Promise.resolve(42)
]).then(console.log); // [1, 2, 42]
promiseAll([
Promise.resolve('ok'),
Promise.reject(new Error('fail')),
]).catch(err => console.error(err.message)); // 'fail'
// Пустой массив разрешается мгновенно
promiseAll([]).then(r => console.assert(r.length === 0));

Promise.allSettled — всегда разрешается массивом статусов:

/**
* Полифилл Promise.allSettled.
* Всегда resolves с массивом { status: 'fulfilled'|'rejected', value|reason }.
* Никогда не rejects.
* @param {Iterable<any>} promises
* @returns {Promise<Array<{status: string, value?: any, reason?: any}>>}
*/
function promiseAllSettled(promises) {
return new Promise(resolve => {
const items = [...promises];
if (items.length === 0) return resolve([]);
const results = new Array(items.length);
let remaining = items.length;
const settle = (i, outcome) => {
results[i] = outcome;
if (--remaining === 0) resolve(results);
};
items.forEach((item, i) => {
Promise.resolve(item).then(
value => settle(i, { status: 'fulfilled', value }),
reason => settle(i, { status: 'rejected', reason }),
);
});
});
}
// usage
promiseAllSettled([
Promise.resolve(1),
Promise.reject(new Error('oops')),
Promise.resolve(3),
]).then(results => {
// [
// { status: 'fulfilled', value: 1 },
// { status: 'rejected', reason: Error: oops },
// { status: 'fulfilled', value: 3 },
// ]
results.forEach(r => {
if (r.status === 'fulfilled') console.log('', r.value);
else console.error('', r.reason.message);
});
});
// Отличие от Promise.all:
// .all → rejects при первой ошибке → fail-fast, нужен все-или-ничего
// .allSettled → всегда resolves → нужно знать результат каждой операции

Сложность

  • Время: O(n) инициализация; итоговое время = max(время всех промисов)
  • Память: O(n) для хранения промежуточных результатов

Итог: Promise.all: счётчик remaining, results[i] по индексу (не push!), первый reject пробрасывается. Promise.allSettled: никогда не rejects, каждый результат в {status, value/reason}.