Кратко
СкопированоМетод all
— это один из статических методов объекта Promise
. Метод all
используют, когда нужно запустить несколько промисов параллельно и дождаться их выполнения.
Как пишется
СкопированоPromise
принимает итерируемую коллекцию промисов (чаще всего — массив) и возвращает новый промис, который будет выполнен, когда будут выполнены все промисы, переданные в виде перечисляемого аргумента, или отклонён, если хотя бы один из переданных промисов завершится с ошибкой.
Метод Promise
возвращает массив значений всех переданных промисов, при этом сохраняя порядок оригинального (переданного) массива, но не порядок выполнения.
Как понять
СкопированоУспешное выполнение нескольких промисов
СкопированоСоздадим несколько промисов:
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
:
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
будет выполнен немедленно.
Один из промисов завершился ошибкой
СкопированоЕсли хотя бы один промис из переданного массива завершится с ошибкой, то Promise
тоже завершится с этой ошибкой. Метод уже не будет следить за выполнением оставшихся промисов, которые рано или поздно все-таки выполнятся, и их результаты будут просто проигнорированы.
В примере ниже, промис 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
передать не промисы, он вернёт переданные не промисы в массив результатов как есть (под капотом при этом произойдёт его преобразование с помощью метода Promise
).
Передадим в Promise
промис 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
. Это позволит дождаться выполнения всех промисов, а затем обработать результат.
Например, можете использовать метод Promise
для получения данных нескольких персонажей из вселенной Звёздных войн через запрос к 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.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.forEach(resp => { console.log(resp.name) }) }) .catch(error => { console.error(error) })
Пример сначала сделает три запроса к API, с помощью Promise
дождётся их завершения и парсинга ответа в JSON, а затем выведет имя персонажа для каждого. В консоли появится:
Luke Skywalker Chewbacca R2-D2
На собеседовании
Скопировано отвечает
СкопированоТеория
СкопированоДля начала вспомним работу оригинального Promise
.
Он принимает коллекцию промисов, начинает одновременно их выполнять и возвращает новый промис. Если все переданные промисы выполнятся, возвращаемый промис тоже выполнится и в нём будет лежать массив результатов, причём в том же порядке. Но! Если какой-то промис вылетел с ошибкой, то Promise
прекращает работу раньше и возвращаемый промис будет отклонён.
Таким образом у нас есть два сценария:
- позитивный, когда все промисы завершились успешно. Тут в ответ придёт массив результатов с сохранением очерёдности;
- негативный, когда какой-то промис завершился с ошибкой. Тут
Promise
не будет ждать завершение оставшихся, а сразу перейдёт в состояние. all rejected
с полученной ошибкой.
Подсказки
СкопированоСначала попробуйте решить самостоятельно. Но, если не получается, то вот подсказки:
- Где-то нужно хранить результаты и как-то отслеживать, что все промисы завершились.
- Возвращаться должен тоже промис.
- По завершению каждого промиса не забываем:
- запомнить результат под правильным индексом;
- отметить, что активных промисов стало меньше на один.
- Случай, когда у нас произошла ошибка, обрабатывать отдельно не нужно. Возвращаемый промис автоматически перейдёт в состояние
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 */