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

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

Скопировано

Это партнёрская рубрика, мы выпускаем её совместно с сервисом онлайн-образования Яндекс Практикум. Приносите вопрос, на который не знаете ответа, в задачи, мы разложим всё по полочкам и опубликуем. Если знаете ответ, присылайте пулреквест на GitHub.

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

Скопировано

Теория

Скопировано

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

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

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

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

Подсказки

Скопировано

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

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

Решение

Скопировано

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

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

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

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

          // Кладём его в наше хранилище
          // Причём сохраняем индекс, под которым он был в массиве `promises`
          results[index] = result

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

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

        
        
          
        
      

Тесты

Скопировано

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

        
          
          // Сделаем искусственную задержку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
*/