Learn JavaScript

Promise.all() - ожидание нескольких промисов в JavaScript

Promise.all() нужен, чтобы дождаться сразу несколько промисов и получить их результаты вместе. Метод вызывается у Promise, принимает набор значений и возвращает новый промис, который успешно завершится только тогда, когда успешно завершатся все элементы.

Синтаксис

Базовая запись выглядит так:

Promise.all(iterable)

Где:

  • iterable - итерируемый набор значений, чаще всего массив

Обычно в Promise.all() передают массив промисов, но внутри могут быть и обычные значения.

Что возвращает Promise.all()

Метод всегда возвращает объект Promise.

Если все элементы завершатся успешно, результатом будет массив их значений.

const promise = Promise.all([
  Promise.resolve("A"),
  Promise.resolve("B"),
]);

console.log(promise instanceof Promise); // true

promise.then((values) => {
  console.log(values); // ["A", "B"]
});

Если хотя бы один элемент завершится с ошибкой, итоговый промис станет rejected.

Простой пример

Promise.all([
  Promise.resolve("Пользователь"),
  Promise.resolve("Настройки"),
]).then((values) => {
  console.log(values); // ["Пользователь", "Настройки"]
});

Это удобный способ собрать результаты нескольких асинхронных операций в одном месте.

Promise.all() дожидается, пока успешно завершатся все элементы

Это главный смысл метода.

const userPromise = Promise.resolve("Анна");
const rolePromise = Promise.resolve("admin");

Promise.all([userPromise, rolePromise]).then((values) => {
  console.log(values); // ["Анна", "admin"]
});

Итоговый then() выполнится только после того, как оба значения будут готовы.

Порядок результатов сохраняется по входному массиву

Это очень важный нюанс.

Promise.all() не сортирует результат по скорости выполнения. Он сохраняет порядок именно таким, каким элементы были переданы.

const first = new Promise((resolve) => {
  setTimeout(() => resolve("first"), 50);
});

const second = new Promise((resolve) => {
  setTimeout(() => resolve("second"), 10);
});

Promise.all([first, second]).then((values) => {
  console.log(values); // ["first", "second"]
});

Хотя second завершился раньше, в массиве он всё равно остался на второй позиции.

Обычные значения тоже работают

Это ещё один полезный момент.

В Promise.all() можно передавать не только промисы, но и обычные значения.

Promise.all([1, Promise.resolve(2), 3]).then((values) => {
  console.log(values); // [1, 2, 3]
});

Обычные значения будут обработаны так, будто они уже успешно готовы.

Если один элемент завершится с ошибкой, весь Promise.all() завершится с ошибкой

Это поведение нужно помнить особенно хорошо.

Promise.all([
  Promise.resolve("ok"),
  Promise.reject(new Error("boom")),
  Promise.resolve("done"),
])
  .then((values) => {
    console.log(values);
  })
  .catch((error) => {
    console.log(error.message); // "boom"
  });

Для успешного результата нужны все элементы без исключения.

Promise.all() берёт первую ошибку и не ждёт итогового успеха остальных

Как только один из элементов отклоняется, итоговый промис сразу переходит в состояние rejected.

const slow = new Promise((resolve) => {
  setTimeout(() => resolve("slow"), 50);
});

const fastReject = new Promise((_, reject) => {
  setTimeout(() => reject(new Error("boom")), 10);
});

Promise.all([slow, fastReject]).catch((error) => {
  console.log(error.message); // "boom"
});

Это удобно, если операция имеет смысл только при полном успехе всех частей.

Остальные промисы при ошибке не отменяются автоматически

Это очень важный практический нюанс.

Даже если Promise.all() уже перешёл в rejected, другие запущенные операции могут продолжать выполняться.

const slow = new Promise((resolve) => {
  setTimeout(() => {
    console.log("slow done");
    resolve("slow");
  }, 50);
});

const fastReject = new Promise((_, reject) => {
  setTimeout(() => reject(new Error("boom")), 10);
});

Promise.all([slow, fastReject]).catch((error) => {
  console.log("catch", error.message); // "catch boom"
});

Сначала вы увидите ошибку из Promise.all(), но затем может выполниться и оставшийся код внутри других промисов.

Пустой массив даёт успешный результат []

Это полезно помнить.

Promise.all([]).then((values) => {
  console.log(values); // []
});

Если передать пустой массив, результатом будет успешно завершённый промис с пустым массивом.

При этом обработчик then() всё равно выполнится асинхронно:

console.log("start");

Promise.all([]).then((values) => {
  console.log(values); // []
});

console.log("end");

Результат будет таким:

start
end
[]

Promise.all() не заменяет Promise.allSettled()

Это полезно различать.

  • Promise.all() подходит, когда нужен успех всех элементов
  • Promise.allSettled() подходит, когда нужно дождаться вообще всех результатов, даже если часть завершилась с ошибкой

Если нужно получить информацию по каждому элементу независимо от ошибок, Promise.all() обычно не подходит.

Когда это полезно

Promise.all() особенно удобен, когда:

  • нужно запустить несколько запросов параллельно
  • важен общий результат сразу всех операций
  • нужно собрать данные из нескольких источников в один шаг
  • операция должна считаться успешной только при полном успехе всех частей

Пример:

Promise.all([
  fetch("/api/user").then((response) => response.json()),
  fetch("/api/settings").then((response) => response.json()),
])
  .then(([user, settings]) => {
    console.log(user, settings);
  })
  .catch((error) => {
    console.log("Ошибка загрузки:", error);
  });

Такой код особенно удобен, когда обе загрузки независимы и их можно делать параллельно.

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

Думать, что результаты приходят в порядке завершения

const first = new Promise((resolve) => {
  setTimeout(() => resolve("first"), 50);
});

const second = new Promise((resolve) => {
  setTimeout(() => resolve("second"), 10);
});

Promise.all([first, second]).then((values) => {
  console.log(values); // ["first", "second"]
});

Порядок результата зависит от входного массива, а не от скорости выполнения.

Ожидать частичный успешный результат при одной ошибке

Promise.all([
  Promise.resolve("ok"),
  Promise.reject(new Error("boom")),
])
  .then((values) => {
    console.log(values);
  })
  .catch((error) => {
    console.log(error.message); // "boom"
  });

Если хотя бы один элемент отклонится, общего массива результатов не будет.

Думать, что обычные значения внутри массива недопустимы

Promise.all([1, Promise.resolve(2), 3]).then((values) => {
  console.log(values); // [1, 2, 3]
});

Обычные значения допустимы и просто считаются уже готовыми.

Думать, что остальные операции автоматически остановятся после первой ошибки

const slow = new Promise((resolve) => {
  setTimeout(() => {
    console.log("slow done");
    resolve("slow");
  }, 50);
});

const fastReject = new Promise((_, reject) => {
  setTimeout(() => reject(new Error("boom")), 10);
});

Promise.all([slow, fastReject]).catch((error) => {
  console.log(error.message); // "boom"
});

Promise.all() завершится ошибкой, но уже запущенные операции могут продолжить выполняться.

Ожидать синхронный then() у пустого массива

console.log("A");

Promise.all([]).then(() => {
  console.log("done");
});

console.log("B");

Сначала выведется A, потом B, и только потом done.

Быстрая памятка

  • Promise.all() вызывается у Promise
  • метод всегда возвращает объект Promise
  • успешным результатом будет массив значений
  • порядок результата совпадает с порядком входных элементов
  • обычные значения внутри массива допустимы
  • если один элемент завершится с ошибкой, весь Promise.all() станет rejected
  • остальные промисы при этом не отменяются автоматически
  • пустой массив даёт успешный результат []
  • then() после Promise.all() всё равно выполняется асинхронно

Коротко

Promise.all() в JavaScript нужен, чтобы дождаться сразу несколько промисов и получить их результаты одним массивом. Главное, что стоит запомнить: метод сохраняет порядок входных элементов, требует успешного завершения всех операций, падает на первой ошибке, не отменяет уже запущенные промисы автоматически и особенно полезен для параллельной загрузки независимых данных.