← Назад к списку
ПрограммированиеFrontendMiddle

Реализуйте собственный Promise.all. Как он поведёт себя при ошибке одного из промисов?

Короткий ответ

  • Принимает массив, возвращает промис с массивом результатов
  • Порядок результатов соответствует порядку входных промисов
  • Заполнять results по индексу, не через push
  • Считать завершённые счётчиком, resolve при полном наборе
  • Первый reject немедленно отклоняет общий промис
  • Обычные значения оборачивать через Promise.resolve
  • Пустой массив — сразу resolve([])

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

Как сказать вслух

пример ответа

Моя реализация возвращает новый промис и подписывается на каждый элемент массива. Результаты я записываю строго по индексу, потому что промисы завершаются в произвольном порядке, а отдать их нужно в исходном. Отдельным счётчиком считаю, сколько уже выполнилось, и когда счётчик сравнялся с длиной — делаю resolve. Если любой промис упал, сразу отклоняю общий промис этой ошибкой, остальные результаты игнорируются. Ещё учитываю, что в массиве могут быть обычные значения — оборачиваю их в Promise.resolve.

Подробный ответ

Основной ответ

Каркас: функция возвращает new Promise((resolve, reject) => ...), внутри — массив results фиксированной длины и счётчик done. По каждому элементу вызываем Promise.resolve(item).then(...), что корректно обрабатывает и не-промисы, и thenable. В обработчике успеха пишем results[index] = value и инкрементируем done; когда done === items.length, вызываем resolve(results). Ошибку пробрасываем в reject напрямую — семантика fail-fast: первый отказ решает судьбу всего, хотя остальные промисы продолжают выполняться, их результаты просто отбрасываются (отмены в промисах нет). Краевые случаи: пустой массив должен немедленно дать resolve([]); нельзя использовать results.push — порядок сломается; нельзя сравнивать results.length — у разреженного массива она врёт. Следующий вопрос интервьюера обычно про Promise.allSettled (ждёт всех, отдаёт статусы) — отличный повод показать, что вы знаете, когда частичные отказы допустимы.

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

  • Порядок по индексу. Запись results[index] гарантирует исходный порядок независимо от скорости завершения промисов.
  • Счётчик, а не length. Готовность определяется счётчиком завершённых; length разреженного массива даёт ложный сигнал.
  • Fail-fast. Первый reject отклоняет общий промис; остальные операции не отменяются, но игнорируются.
  • Краевые случаи. Пустой массив, обычные значения и thenable во входе — то, на чём проверяют глубину реализации.

Практический контекст

Классика лайв-кодинга: проверяет одновременно понимание промисов, замыканий в цикле и аккуратность с краевыми случаями. В реальной работе паттерн «собрать N параллельных запросов» встречается постоянно — загрузка данных нескольких виджетов, батчи изображений. Полезно уметь продолжить разговор: когда вместо all нужен allSettled, как ограничить конкурентность пулом из k одновременных запросов.

Пример кода

function promiseAll(items) {
  return new Promise((resolve, reject) => {
    const results = new Array(items.length);
    let done = 0;
    if (items.length === 0) return resolve(results);
    items.forEach((item, i) => {
      Promise.resolve(item).then(value => {
        results[i] = value;
        if (++done === items.length) resolve(results);
      }, reject);
    });
  });
}

Частые ошибки

  • Используют push и получают результаты в порядке завершения, а не в исходном
  • Забывают случай пустого массива — промис зависает навсегда
  • Не оборачивают элементы в Promise.resolve и падают на обычных значениях

ИП Кочкин Алексей Сергеевич · ИНН 390509026279 · ОГРНИП 325390000030973 · jiniys2005@yandex.ru