Кратко
СкопированоМетод 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 — массив результатов выполнения промисов 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 дождётся их завершения и парсинга ответа в JSON, а затем выведет имя персонажа для каждого. В консоли появится:
Luke Skywalker Chewbacca R2-D2
На собеседовании
Скопировано
отвечает
СкопированоТеория
СкопированоДля начала вспомним работу оригинального Promise.
Он принимает коллекцию промисов, начинает одновременно их выполнять и возвращает новый промис. Если все переданные промисы выполнятся, возвращаемый промис тоже выполнится и в нём будет лежать массив результатов, причём в том же порядке. Но! Если какой-то промис вылетел с ошибкой, то Promise прекращает работу раньше и возвращаемый промис будет отклонён.
Таким образом у нас есть два сценария:
- Позитивный: Когда все промисы завершились успешно. Тут в ответ придёт массив результатов с сохранением очерёдности.
- Негативный: Когда какой-то промис завершился с ошибкой. Тут
Promiseне будет ждать завершение оставшихся, а сразу перейдёт в состояние. all rejectedс полученной ошибкой.
Подсказки
СкопированоСначала попробуйте решить самостоятельно. Но если не получается, то вот подсказки:
- Где-то нужно хранить результаты и как-то отслеживать, что все промисы завершились.
- Возвращаться должен тоже промис.
- По завершению каждого промиса не забываем:
- запомнить результат под правильным индексом
- отметить, что активных промисов стало меньше на один
- Случай, когда у нас произошла ошибка, обрабатывать отдельно не нужно — возвращаемый промис автоматически перейдёт в состояние
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
*/