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) => {  // Здесь будем хранить результаты успешно завершенных промисов  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
*/

        
        
          
        
      

Алексей Фомин  отвечает

Скопировано

Теория

Скопировано

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

Он принимает коллекцию промисов, начинает одновременно их выполнять и возвращает массив результатов исполнения полученных промисов. Объект каждого результата содержит свойство status. Если status имеет значение fulfilled (выполнено), то объект будет содержать свойство value. Если status имеет значение rejected (отклонено), то объект будет содержать свойство reason. Свойства value или reason будут содержать значение, с которым был выполнен или отклонён промис.

Подсказки

Скопировано

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

  1. Можно воспользоваться методом Promse.all.
  2. Возвращаться должен массив с объектами.
  3. По завершению каждого промиса:
  • если промис был выполнен, то запипишем в поле status значение fulfilled, а в поле value — результат выполнения промиса.
  • если промис был отклонён, то запипишем в поле status значение rejected, а в поле reason — значение с текущей ошибкой, по которой он был отклонён.

Решение

Скопировано

Напишем код:

        
          
          // На вход приходит массив промисовPromise.allSettled = (promises) => {  // Запустим все промисы с помощью .all  return Promise.all(    promises.map((promise) =>      Promise.resolve(promise)        // Если промис завершается успешно        .then((value) => ({ status: "fulfilled", value }))        // Если промис был отклонён        .catch((e) => ({ status: "rejected", reason: e }))    )  )}
          // На вход приходит массив промисов
Promise.allSettled = (promises) => {
  // Запустим все промисы с помощью .all
  return Promise.all(
    promises.map((promise) =>
      Promise.resolve(promise)
        // Если промис завершается успешно
        .then((value) => ({ status: "fulfilled", value }))
        // Если промис был отклонён
        .catch((e) => ({ status: "rejected", reason: e }))
    )
  )
}

        
        
          
        
      

Тесты

Скопировано

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

        
          
          // Создадим несколько промисов для тестированияconst a = new Promise((resolve) => setTimeout(() => { resolve(3) },200))const b = new Promise((resolve,reject) => reject('error'))const c = new Promise((resolve) => resolve(5))// ВыполнимPromise  .allSettled([a, b, c])  .then(console.log)/*[{status: 'fulfilled', value: 3},{status: 'rejected', reason: 'error'},{status: 'fulfilled', value: 5}]*/
          // Создадим несколько промисов для тестирования
const a = new Promise((resolve) => setTimeout(() => { resolve(3) },200))
const b = new Promise((resolve,reject) => reject('error'))
const c = new Promise((resolve) => resolve(5))

// Выполним
Promise
  .allSettled([a, b, c])
  .then(console.log)

/*
[
{status: 'fulfilled', value: 3},
{status: 'rejected', reason: 'error'},
{status: 'fulfilled', value: 5}
]
*/