Promise.all()

Запускаем несколько промисов параллельно и ждём, когда они все выполнятся.

Время чтения: меньше 5 мин

Кратко

Скопировано

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

Как пишется

Скопировано

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

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

Как понять

Скопировано

Успешное выполнение нескольких промисов

Скопировано

Создадим несколько промисов:

        
          
          const promise1 = new Promise(resolve => setTimeout(() => resolve(1), 5000))const promise2 = new Promise(resolve => setTimeout(() => resolve(2), 2000))const promise3 = new Promise(resolve => setTimeout(() => resolve(3), 1000))
          const promise1 = new Promise(resolve => setTimeout(() => resolve(1), 5000))
const promise2 = new Promise(resolve => setTimeout(() => resolve(2), 2000))
const promise3 = new Promise(resolve => setTimeout(() => resolve(3), 1000))

        
        
          
        
      

Передадим массив из созданных промисов в Promise.all():

        
          
          Promise.all([promise1, promise2, promise3])  .then(([response1, response2, response3]) => {    console.log(response1)    // 1    console.log(response2)    // 2    console.log(response3)    // 3  })
          Promise.all([promise1, promise2, promise3])
  .then(([response1, response2, response3]) => {
    console.log(response1)
    // 1
    console.log(response2)
    // 2
    console.log(response3)
    // 3
  })

        
        
          
        
      

Если передать пустой массив, то Promise.all() будет выполнен немедленно.

Один из промисов завершился ошибкой

Скопировано

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

В примере ниже, промис promise2 завершается с ошибкой:

        
          
          const promise1 = new Promise(resolve => setTimeout(() => resolve(1), 5000))const promise2 = new Promise((resolve, reject) => setTimeout(() => reject('error'), 2000))const promise3 = new Promise(resolve => setTimeout(() => resolve(3), 1000))Promise.all([promise1, promise2, promise3])  .then(([response1, response2, response3 ]) => {    console.log(response1)    console.log(response2)    console.log(response3)  })  .catch(error => {    console.error(error)    // error  })
          const promise1 = new Promise(resolve => setTimeout(() => resolve(1), 5000))
const promise2 = new Promise((resolve, reject) => setTimeout(() => reject('error'), 2000))
const promise3 = new Promise(resolve => setTimeout(() => resolve(3), 1000))

Promise.all([promise1, promise2, promise3])
  .then(([response1, response2, response3 ]) => {
    console.log(response1)
    console.log(response2)
    console.log(response3)
  })
  .catch(error => {
    console.error(error)
    // error
  })

        
        
          
        
      

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

Не промисы в массиве промисов

Скопировано

Если в Promise.all() передать не промисы, он вернёт переданные не промисы в массив результатов как есть (под капотом при этом произойдёт его преобразование с помощью метода Promise.resolve()).

Передадим в Promise.all() промис promise1, число number и объект obj:

        
          
          const promise1 = new Promise(resolve => setTimeout(() => resolve(1), 5000))const number = 2const obj = {key: 'value'}Promise.all([promise1, number, obj])  .then(([response1, response2, response3]) => {    console.log(response1)    // 1    console.log(response2)    // 2    console.log(response3.key)    // 'value'  })
          const promise1 = new Promise(resolve => setTimeout(() => resolve(1), 5000))
const number = 2
const obj = {key: 'value'}

Promise.all([promise1, number, obj])
  .then(([response1, response2, response3]) => {
    console.log(response1)
    // 1
    console.log(response2)
    // 2
    console.log(response3.key)
    // 'value'
  })

        
        
          
        
      

На практике

Скопировано

Роман Юрлов советует

Скопировано

🛠 Довольно частое использование — это преобразование массива с данными в массив с промисами с помощью map(). В map() для каждого элемента создаётся промис, а затем полученный массив передаётся в Promise.all(). Это позволит дождаться выполнения всех промисов, а затем обработать результат.

Например, можно использовать метод Promise.all() для получения данных нескольких персонажей из вселенной звёздных войн через запрос к API:

        
          
          const peopleIds = [1, 13, 3]const arrayFetchUsers = peopleIds.map(user => fetch(`https://swapi.dev/api/people/${user}`).then((response) => response.json()))Promise.all(arrayFetchUsers)  .then((responses) => {    // responses — массив результатов выполнения промисов    responses.forEach(resp => {      console.log(resp.name)    })  })  .catch(error => {    console.error(error)  })
          const peopleIds = [1, 13, 3]
const arrayFetchUsers = peopleIds.map(user => fetch(`https://swapi.dev/api/people/${user}`).then((response) => response.json()))

Promise.all(arrayFetchUsers)
  .then((responses) => {
    // responses — массив результатов выполнения промисов
    responses.forEach(resp => {
      console.log(resp.name)
    })
  })
  .catch(error => {
    console.error(error)
  })

        
        
          
        
      

Пример сначала сделает три запроса к API, с помощью Promise.all() дождётся их завершения и парсинга ответа в JSON, а затем выведет имя персонажа для каждого. В консоли появится:

Luke Skywalker
Chewbacca
R2-D2

На собеседовании

Скопировано
Задать вопрос в рубрику
🤚 Я знаю ответ

Наставник Практикума
Борис Южаков  отвечает

Скопировано

Теория

Скопировано

Для начала вспомним работу оригинального Promise.all.

Он принимает коллекцию промисов, начинает одновременно их выполнять и возвращает новый промис. Если все переданные промисы выполнятся, возвращаемый промис тоже выполнится и в нём будет лежать массив результатов, причём в том же порядке. Но! Если какой-то промис вылетел с ошибкой, то Promise.all прекращает работу раньше и возвращаемый промис будет отклонён.

Таким образом у нас есть два сценария:

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

Подсказки

Скопировано

Сначала попробуйте решить самостоятельно. Но, если не получается, то вот подсказки:

  1. Где-то нужно хранить результаты и как-то отслеживать, что все промисы завершились.
  2. Возвращаться должен тоже промис.
  3. По завершению каждого промиса не забываем:
  • запомнить результат под правильным индексом;
  • отметить, что активных промисов стало меньше на один.
  1. Случай, когда у нас произошла ошибка, обрабатывать отдельно не нужно. Возвращаемый промис автоматически перейдёт в состояние rejected.

Решение

Скопировано

Напишем код и прокомментируем каждую строчку.

        
          
          // На вход к нам приходит массив промисовPromise.all = (promises) => {  // Если передали пустой массив или строку  if (promises.length === 0) {    return Promise.resolve([])  }  // Здесь будем хранить результаты успешно завершённых промисов  const results = []  // Количество промисов, которые осталось выполнить.  // Пока ещё не выполнился ни один промис  let rest = promises.length  // Возвращаем новый промис  return new Promise((resolve, reject) => { // <= добавить reject, так он должен обрабатывать ошибки    // Проходимся по списочку    promises.forEach((promise, index) => {      Promise.resolve(promise) // <= промисами могут быть и другие типы данных        // Если промис завершается успешно        .then((result) => {          // Кладём его в наше хранилище. При этом сохраняем индекс,          // под которым он был в массиве `promises`          results[index] = result          // Стало меньше на один невыполненный промис          rest -= 1          // Если активных промисов больше нет, резолвим результат          if (rest === 0) resolve(results)        }, reject) // <= достаточно передать reject как функцию    })  })}
          // На вход к нам приходит массив промисов
Promise.all = (promises) => {
  // Если передали пустой массив или строку
  if (promises.length === 0) {
    return Promise.resolve([])
  }

  // Здесь будем хранить результаты успешно завершённых промисов
  const results = []

  // Количество промисов, которые осталось выполнить.
  // Пока ещё не выполнился ни один промис
  let rest = promises.length

  // Возвращаем новый промис
  return new Promise((resolve, reject) => { // <= добавить reject, так он должен обрабатывать ошибки
    // Проходимся по списочку
    promises.forEach((promise, index) => {
      Promise.resolve(promise) // <= промисами могут быть и другие типы данных

        // Если промис завершается успешно
        .then((result) => {
          // Кладём его в наше хранилище. При этом сохраняем индекс,
          // под которым он был в массиве `promises`
          results[index] = result

          // Стало меньше на один невыполненный промис
          rest -= 1

          // Если активных промисов больше нет, резолвим результат
          if (rest === 0) resolve(results)
        }, reject) // <= достаточно передать reject как функцию
    })
  })
}

        
        
          
        
      

Тесты

Скопировано

Теперь протестируем наш полифил

        
          
          // Сделаем искусственную задержкуconst delay = (timeout) => new Promise((resolve) => setTimeout(resolve, timeout))// И создадим несколько промисов для тестированияconst fetchUsers = () => delay(1000).then(() => [ 'Маша', 'Петя', 'Оля' ])const problemPromise = delay(2000).then(() => { throw 'Houston, we have a problem' })const stringPromise = delay(5000).then(() => 'Lorem ipsum dolor sit amet')// Прогоним позитивный случайPromise  .all([ fetchUsers(), stringPromise ])  .then(console.log)/*[  [ 'Маша', 'Петя', 'Оля' ],  'Lorem ipsum dolor sit amet']*/// Прогоним негативный случай, когда возникает ошибкаPromise  .all([ fetchUsers(), problemPromise, stringPromise ])  .then(console.log)/* Uncaught (in promise) Houston, we have a problem */
          // Сделаем искусственную задержку
const delay = (timeout) => new Promise((resolve) => setTimeout(resolve, timeout))

// И создадим несколько промисов для тестирования
const fetchUsers = () => delay(1000).then(() => [ 'Маша', 'Петя', 'Оля' ])
const problemPromise = delay(2000).then(() => { throw 'Houston, we have a problem' })
const stringPromise = delay(5000).then(() => 'Lorem ipsum dolor sit amet')

// Прогоним позитивный случай
Promise
  .all([ fetchUsers(), stringPromise ])
  .then(console.log)
/*
[
  [ 'Маша', 'Петя', 'Оля' ],
  'Lorem ipsum dolor sit amet'
]
*/

// Прогоним негативный случай, когда возникает ошибка
Promise
  .all([ fetchUsers(), problemPromise, stringPromise ])
  .then(console.log)
/* Uncaught (in promise) Houston, we have a problem */