← Назад к вопросам

Реализуйте собственный 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);
        });
    });
  });
}

Как это работает

Ключевые моменты:

  1. Порядок результатов — используем индекс для сохранения порядка
  2. Счётчик завершённых — отслеживаем, когда все промисы выполнены
  3. Первая ошибка — при реджекте сразу отклоняем весь результат
  4. Пустой массив — обрабатываем edge case
  5. 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.allpromiseAll
РеализацияВстроенная, оптимизированаКастомная
ПараллельностьДа, все промисы выполняются одновременноДа, если правильно реализовано
Порядок результатовСохраняетсяСохраняется
Ошибка в массивеРеджектит сразуРеджектит сразу
Пустой массивРезолвит []Резолвит []

Важные замечания

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, он оптимизирован движком.

Реализуйте собственный Promise.all | PrepBro