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 пробрасывается наружу ); }); });}
// usagepromiseAll([ 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 }), ); }); });}
// usagepromiseAllSettled([ 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}.