Кратко
СкопированоМетод any — один из статических методов объекта Promise. Его используют, когда нужно запустить несколько промисов параллельно и дождаться первого успешного разрешённого.
Как пишется
СкопированоPromise принимает итерируемую коллекцию промисов, чаще всего — массив. Метод возвращает новый промис, который выполнится, когда выполнится первый из промисов, переданных в виде перечисляемого аргумента. Промис может быть отклонён, если все из переданных промисов завершатся с ошибкой.
Возвращает значение первого успешно выполнившегося промиса.
Как понять
СкопированоМетод полезен, когда нужно вернуть первый исполненный промис. После того как один из промисов будет исполнен, метод не будет дожидаться исполнения остальных.
В отличие от Promise, который содержит массив значений исполненных промисов, Promise содержит только одно значение при условии, что хотя бы один из промисов исполнен успешно. Такой подход полезен, когда нужно, чтобы выполнился только один промис, неважно какой.
Также Promise отличается от Promise. Метод Promise возвращает промис, содержащий значение первого завершённого (resolved или rejected). Метод Promise возвращает промис, содержащий значение первого успешно выполненного (resolved) промиса. Promise проигнорирует исполнение промисов с ошибкой (rejection) до первого исполненного успешного (resolved).
Можете использовать Promise, когда есть две картинки и надо отобразить ту, которая загрузится быстрее:
function fetchAndDecode(url) { return fetch(url) .then(response => { if (!response.ok) { throw new Error(`HTTP error! status: ${response.status}`) } else { return response.blob() } })}const coffee = fetchAndDecode('coffee.jpg')const tea = fetchAndDecode('tea.jpg')Promise.any([coffee, tea]) .then((value) => { const objectURL = URL.createObjectURL(value) const image = document.createElement('img') image.src = objectURL document.body.appendChild(image) }) .catch((error) => { console.error(error) })
function fetchAndDecode(url) {
return fetch(url)
.then(response => {
if (!response.ok) {
throw new Error(`HTTP error! status: ${response.status}`)
} else {
return response.blob()
}
})
}
const coffee = fetchAndDecode('coffee.jpg')
const tea = fetchAndDecode('tea.jpg')
Promise.any([coffee, tea])
.then((value) => {
const objectURL = URL.createObjectURL(value)
const image = document.createElement('img')
image.src = objectURL
document.body.appendChild(image)
})
.catch((error) => {
console.error(error)
})
Успешное выполнение нескольких промисов. Создадим два промиса, второй выполнится раньше:
const promise1 = new Promise(resolve => setTimeout(() => resolve(1), 5000))const promise2 = new Promise(resolve => setTimeout(() => resolve(2), 1000))
const promise1 = new Promise(resolve => setTimeout(() => resolve(1), 5000))
const promise2 = new Promise(resolve => setTimeout(() => resolve(2), 1000))
Передадим массив из созданных промисов в Promise:
Promise.any([promise1, promise2]) .then((result) => { console.log(result) })// 2
Promise.any([promise1, promise2])
.then((result) => {
console.log(result)
})
// 2
Пустой массив промисов. Передадим пустой массив в Promise:
Promise.any([]) .then((result) => { console.log(result) }) .catch(error => { console.error(error) })
Promise.any([])
.then((result) => {
console.log(result)
})
.catch(error => {
console.error(error)
})
В итоге обработчик then будет проигнорирован, и будет выполняться код из обработчика ошибок catch.
Один из промисов завершился ошибкой. Метод Promise не завершится с ошибкой, если хотя бы один из промисов выполнится успешно.
В примере промис promise2 завершается с ошибкой:
const promise1 = new Promise( resolve => setTimeout(() => resolve(1), 5000))const promise2 = new Promise( (resolve, reject) => setTimeout(() => reject('error'), 1000))Promise.any([promise1, promise2]) .then((result) => { console.log(result) // 1 }) .catch(error => { console.error(error) })
const promise1 = new Promise(
resolve => setTimeout(() => resolve(1), 5000)
)
const promise2 = new Promise(
(resolve, reject) => setTimeout(() => reject('error'), 1000)
)
Promise.any([promise1, promise2])
.then((result) => {
console.log(result)
// 1
})
.catch(error => {
console.error(error)
})
В итоге обработчик catch проигнорируется и выполнится код из обработчика then со значением 1 в переменной result.
Все промисы завершились ошибкой. Метод Promise завершится с ошибкой, если все переданные промисы завершатся с ошибкой.
const promise1 = new Promise( (resolve, reject) => setTimeout(() => reject('error1'), 5000))const promise2 = new Promise( (resolve, reject) => setTimeout(() => reject('error2'), 1000))Promise.any([promise1, promise2]) .then((result) => { console.log(result) }) .catch(error => { console.error(error) // AggregateError: All promises were rejected console.log(error.errors) // ['error1', 'error2'] })
const promise1 = new Promise(
(resolve, reject) => setTimeout(() => reject('error1'), 5000)
)
const promise2 = new Promise(
(resolve, reject) => setTimeout(() => reject('error2'), 1000)
)
Promise.any([promise1, promise2])
.then((result) => {
console.log(result)
})
.catch(error => {
console.error(error)
// AggregateError: All promises were rejected
console.log(error.errors)
// ['error1', 'error2']
})
В итоге обработчик then проигнорируется и выполнится код из обработчика ошибок catch. В этом случае error это экземпляр класса Aggregate. Объект Aggregate содержит ошибки от обоих промисов в массиве errors.
☝️ Порядок в массиве ошибок определяется очерёдностью промисов в исходной коллекции.
Непромисы в массиве промисов. Если в Promise передать что-то помимо промисов, метод вернёт промис, содержащий первый переданный аргумент любого типа как результат выполнения. Под капотом при этом произойдёт его преобразование с помощью метода Promise.
Передадим в Promise массив значений, которые не являются промисами:
const number = 2const obj = {key: 'value'}const func = () => trueconst bool = falseconst nulled = nullconst undef = undefinedPromise.any([number, obj, func, bool, nulled, undef]) .then((result) => { console.log(result) // 2 })Promise.any([obj, func, bool, nulled, undef, number]) .then((result) => { console.log(result) // {key: 'value'} })Promise.any([func, bool, nulled, undef, number, obj]) .then((result) => { console.log(result) // () => true })Promise.any([bool, nulled, undef, number, obj, func]) .then((result) => { console.log(result) // false })Promise.any([nulled, undef, number, obj, func, bool]) .then((result) => { console.log(result) // null })Promise.any([undef, number, obj, func, bool, nulled]) .then((result) => { console.log(result) // undefined })
const number = 2
const obj = {key: 'value'}
const func = () => true
const bool = false
const nulled = null
const undef = undefined
Promise.any([number, obj, func, bool, nulled, undef])
.then((result) => {
console.log(result)
// 2
})
Promise.any([obj, func, bool, nulled, undef, number])
.then((result) => {
console.log(result)
// {key: 'value'}
})
Promise.any([func, bool, nulled, undef, number, obj])
.then((result) => {
console.log(result)
// () => true
})
Promise.any([bool, nulled, undef, number, obj, func])
.then((result) => {
console.log(result)
// false
})
Promise.any([nulled, undef, number, obj, func, bool])
.then((result) => {
console.log(result)
// null
})
Promise.any([undef, number, obj, func, bool, nulled])
.then((result) => {
console.log(result)
// undefined
})
На собеседовании
Скопировано отвечает
СкопированоТеория
СкопированоДля начала вспомним работу оригинального Promise.
Метод Promise принимает коллекцию промисов и возвращает новый промис, который выполняется с результатом самого первого успешно выполненного промиса. Если все промисы завершаются с ошибкой, возвращаемый промис завершается с ошибкой Aggregate, содержащей массив всех ошибок.
Подсказки
СкопированоСначала попробуйте решить самостоятельно. Если не получается, вот подсказки:
- Считаем количество отклонённых промисов, чтобы понять, если все они завершились ошибкой.
- Как только найдём первый успешно выполненный промис, возвращаем его результат.
- Если всё-таки все промисы отклонились, создаём
Aggregateс массивом ошибок и возвращаем его.Error
Решение
СкопированоНапишем реализацию метода Promise:
Promise.any = (iterable) => { // Проверяем, что переданный аргумент итерируемый if (typeof iterable?.[Symbol.iterator] !== 'function') { return Promise.reject(new TypeError('Аргумент должен быть итерируемым')) } // Приводим итерируемый объект к массиву const promises = [...iterable] // Если массив пустой, сразу возвращаем отклонённый промис if (promises.length === 0) { return Promise.reject(new AggregateError([], 'Все промисы были отклонены')) } return new Promise((resolve, reject) => { const errors = [] let rejectedCount = 0 promises.forEach((promise, index) => { // Оборачиваем каждый элемент массива в Promise.resolve, // чтобы корректно обрабатывать непромисы Promise .resolve(promise) .then(resolve) // Резолвим с первым успешным значением .catch((error) => { errors[index] = error // Сохраняем ошибку rejectedCount += 1 // Если все промисы отклонены, формируем AggregateError if (rejectedCount === promises.length) { reject(new AggregateError(errors, 'Все промисы были отклонены')) } }) }) })}
Promise.any = (iterable) => {
// Проверяем, что переданный аргумент итерируемый
if (typeof iterable?.[Symbol.iterator] !== 'function') {
return Promise.reject(new TypeError('Аргумент должен быть итерируемым'))
}
// Приводим итерируемый объект к массиву
const promises = [...iterable]
// Если массив пустой, сразу возвращаем отклонённый промис
if (promises.length === 0) {
return Promise.reject(new AggregateError([], 'Все промисы были отклонены'))
}
return new Promise((resolve, reject) => {
const errors = []
let rejectedCount = 0
promises.forEach((promise, index) => {
// Оборачиваем каждый элемент массива в Promise.resolve,
// чтобы корректно обрабатывать непромисы
Promise
.resolve(promise)
.then(resolve) // Резолвим с первым успешным значением
.catch((error) => {
errors[index] = error // Сохраняем ошибку
rejectedCount += 1
// Если все промисы отклонены, формируем AggregateError
if (rejectedCount === promises.length) {
reject(new AggregateError(errors, 'Все промисы были отклонены'))
}
})
})
})
}
Как это работает
Скопировано- Проверяем, что входные данные являются итерируемыми. Если аргумент не соответствует требованию, возвращаем
Type.Error - Преобразуем итерируемый объект в массив. Если он пустой, сразу возвращаем отклонённый промис с
Aggregate.Error - Создаём массив для хранения ошибок
errorsи счётчик отклонённых промисовrejected.Count - Для каждого промиса:
- Если он выполняется успешно, вызываем
resolveс его результатом. - Если он отклоняется, сохраняем ошибку в
errorsи увеличиваем счётчикrejected.Count
- Если все промисы отклонены, вызываем
rejectсAggregate, содержащим массив всех ошибок.Error
Тесты
СкопированоВспомогательная функция
СкопированоДля тестов используем искусственную задержку:
const delay = (timeout, result, shouldReject = false) => new Promise((resolve, reject) => setTimeout(() => (shouldReject ? reject(result) : resolve(result)), timeout) )
const delay = (timeout, result, shouldReject = false) =>
new Promise((resolve, reject) =>
setTimeout(() => (shouldReject ? reject(result) : resolve(result)), timeout)
)
Пример 1: первый успешный промис
СкопированоПредставьте, что у нас есть три промиса с разным временем выполнения. Один из них завершится с ошибкой:
const promise1 = delay(1000, 'Первый успешный')const promise2 = delay(500, 'Второй успешный')const promise3 = delay(1500, 'Третий с ошибкой', true)Promise.any([promise1, promise2, promise3]) .then(console.log) // Ожидаемый результат: 'Второй успешный' .catch(console.error)
const promise1 = delay(1000, 'Первый успешный')
const promise2 = delay(500, 'Второй успешный')
const promise3 = delay(1500, 'Третий с ошибкой', true)
Promise.any([promise1, promise2, promise3])
.then(console.log) // Ожидаемый результат: 'Второй успешный'
.catch(console.error)
Пример 2: все промисы отклонены
СкопированоТеперь представим, что все промисы завершаются с ошибками:
const allRejected = [ delay(500, 'Ошибка 1', true), delay(1000, 'Ошибка 2', true), delay(1500, 'Ошибка 3', true)]Promise.any(allRejected) .then(console.log) .catch((error) => { console.error(error); // AggregateError: Все промисы были отклонены console.error(error.errors) // ['Ошибка 1', 'Ошибка 2', 'Ошибка 3'] })
const allRejected = [
delay(500, 'Ошибка 1', true),
delay(1000, 'Ошибка 2', true),
delay(1500, 'Ошибка 3', true)
]
Promise.any(allRejected)
.then(console.log)
.catch((error) => {
console.error(error); // AggregateError: Все промисы были отклонены
console.error(error.errors) // ['Ошибка 1', 'Ошибка 2', 'Ошибка 3']
})
Пример 3: число как аргумент
СкопированоПроверим поведение полифила с числом как аргументом:
Promise.any(123) .then(console.log) .catch((error) => console.error(error)) // TypeError: 'Аргумент должен быть итерируемым'
Promise.any(123)
.then(console.log)
.catch((error) => console.error(error)) // TypeError: 'Аргумент должен быть итерируемым'
Пример 4: строка как аргумент
СкопированоИ, наконец, проверим как Promise поведёт себя со строкой:
Promise.any('Тест') .then(console.log) // Ожидаемый результат: 'Т' .catch(console.error)
Promise.any('Тест')
.then(console.log) // Ожидаемый результат: 'Т'
.catch(console.error)