← Назад к вопросам
Реализуйте собственный Promise.all
2.0 Middle🔥 251 комментариев
#Node.js и JavaScript#Алгоритмы и структуры данных
Условие
Напишите функцию promiseAll(promises), которая работает аналогично Promise.all:
- Принимает массив промисов
- Возвращает промис, который резолвится массивом результатов в том же порядке
- Если хотя бы один промис реджектится, весь результат реджектится с той же ошибкой
function promiseAll(promises) {
// Ваш код
}
// Пример использования:
const p1 = Promise.resolve(1);
const p2 = Promise.resolve(2);
const p3 = new Promise(resolve => setTimeout(() => resolve(3), 100));
promiseAll([p1, p2, p3]).then(console.log); // [1, 2, 3]
Что проверяется
- Глубокое понимание промисов
- Работа с асинхронным кодом
- Обработка ошибок в асинхронных операциях
Комментарии (1)
🐱
claude-haiku-4.5PrepBro AI23 мар. 2026 г.(ред.)
Ответ сгенерирован нейросетью и может содержать ошибки
Решение
Базовая реализация
function promiseAll<T>(promises: Promise<T>[]): Promise<T[]> {
return new Promise((resolve, reject) => {
// Если массив пуст, резолвим пустой массив
if (promises.length === 0) {
resolve([]);
return;
}
const results: T[] = [];
let completedCount = 0;
promises.forEach((promise, index) => {
Promise.resolve(promise)
.then((value) => {
results[index] = value;
completedCount++;
// Если все промисы выполнены, резолвим результат
if (completedCount === promises.length) {
resolve(results);
}
})
.catch((error) => {
// При первой ошибке сразу реджектим весь результат
reject(error);
});
});
});
}
Как это работает
Ключевые моменты:
- Порядок результатов — используем индекс для сохранения порядка
- Счётчик завершённых — отслеживаем, когда все промисы выполнены
- Первая ошибка — при реджекте сразу отклоняем весь результат
- Пустой массив — обрабатываем edge case
- Promise.resolve() — поддерживаем и значения, и промисы
Пошаговый процесс:
Вход: [Promise(1), Promise(2), Promise(3)]
Шаг 1: Начало выполнения всех промисов параллельно
├─ Promise(1) → then → results[0] = 1 → completedCount = 1
├─ Promise(2) → then → results[1] = 2 → completedCount = 2
└─ Promise(3) → then → results[2] = 3 → completedCount = 3
Шаг 2: completedCount === length → resolve([1, 2, 3])
Пример использования
const p1 = Promise.resolve(1);
const p2 = Promise.resolve(2);
const p3 = new Promise(resolve => setTimeout(() => resolve(3), 100));
promiseAll([p1, p2, p3])
.then(results => console.log(results)) // [1, 2, 3]
.catch(error => console.error(error));
Обработка ошибок
const p1 = Promise.resolve(1);
const p2 = Promise.reject(new Error('Ошибка!'));
const p3 = Promise.resolve(3);
promiseAll([p1, p2, p3])
.catch(error => console.error(error.message)); // "Ошибка!"
Важно: при ошибке в p2 весь результат реджектится, p3 выполнится, но его результат будет проигнорирован.
Версия с типизацией для разных типов
type Promisable<T> = T | Promise<T> | PromiseLike<T>;
function promiseAll<T extends readonly Promisable<any>[]>(
promises: T
): Promise<{
[K in keyof T]: T[K] extends Promisable<infer U> ? U : never;
}> {
return new Promise((resolve, reject) => {
if (!Array.isArray(promises) || promises.length === 0) {
resolve([] as any);
return;
}
const results = new Array(promises.length);
let completedCount = 0;
promises.forEach((promise, index) => {
Promise.resolve(promise)
.then((value) => {
results[index] = value;
completedCount++;
if (completedCount === promises.length) {
resolve(results);
}
})
.catch(reject);
});
});
}
Альтернативная реализация с async/await
async function promiseAll<T>(promises: Promise<T>[]): Promise<T[]> {
try {
return await Promise.all(promises);
} catch (error) {
throw error;
}
}
// Или более явная реализация:
async function promiseAll<T>(promises: Promise<T>[]): Promise<T[]> {
if (promises.length === 0) return [];
const results: T[] = [];
for (let i = 0; i < promises.length; i++) {
try {
results[i] = await promises[i];
} catch (error) {
throw error;
}
}
return results;
}
Но эта версия НЕПРАВИЛЬНА — она выполняет промисы ПОСЛЕДОВАТЕЛЬНО, а не параллельно!
Правильная реализация с async/await
async function promiseAll<T>(promises: Promise<T>[]): Promise<T[]> {
const wrappedPromises = promises.map(async (promise, index) => {
return { index, value: await promise };
});
try {
const results = await Promise.all(wrappedPromises);
const sorted = new Array(promises.length);
results.forEach(({ index, value }) => {
sorted[index] = value;
});
return sorted;
} catch (error) {
throw error;
}
}
Тестирование
import { describe, it, expect, vi } from 'vitest';
describe('promiseAll', () => {
it('должен разрешить массив результатов', async () => {
const p1 = Promise.resolve(1);
const p2 = Promise.resolve(2);
const p3 = Promise.resolve(3);
const result = await promiseAll([p1, p2, p3]);
expect(result).toEqual([1, 2, 3]);
});
it('должен вернуть пустой массив для пустого входа', async () => {
const result = await promiseAll([]);
expect(result).toEqual([]);
});
it('должен отклонить при ошибке', async () => {
const p1 = Promise.resolve(1);
const p2 = Promise.reject(new Error('Fail'));
const p3 = Promise.resolve(3);
await expect(promiseAll([p1, p2, p3])).rejects.toThrow('Fail');
});
it('должен сохранить порядок результатов', async () => {
const p1 = new Promise(resolve => setTimeout(() => resolve(1), 100));
const p2 = Promise.resolve(2);
const p3 = new Promise(resolve => setTimeout(() => resolve(3), 50));
const result = await promiseAll([p1, p2, p3]);
expect(result).toEqual([1, 2, 3]);
});
it('должен обработать не-промисы', async () => {
const result = await promiseAll([1, Promise.resolve(2), 3]);
expect(result).toEqual([1, 2, 3]);
});
});
Promise.all vs promiseAll
| Аспект | Promise.all | promiseAll |
|---|---|---|
| Реализация | Встроенная, оптимизирована | Кастомная |
| Параллельность | Да, все промисы выполняются одновременно | Да, если правильно реализовано |
| Порядок результатов | Сохраняется | Сохраняется |
| Ошибка в массиве | Реджектит сразу | Реджектит сразу |
| Пустой массив | Резолвит [] | Резолвит [] |
Важные замечания
1. Параллельность:
// ПРАВИЛЬНО — все промисы выполняются одновременно
const results = await promiseAll([p1, p2, p3]);
// НЕПРАВИЛЬНО — выполняются последовательно!
const r1 = await p1;
const r2 = await p2;
const r3 = await p3;
2. Ошибки: Промис.all реджектит при ПЕРВОЙ ошибке, остальные промисы продолжают выполняться в фоне.
3. Производительность: Для реального использования используйте встроенный Promise.all, он оптимизирован движком.