Промисы: основы #
Промис (Promise) — объект, представляющий результат асинхронной операции, который может быть доступен сейчас, позже или никогда. Вместо вложенных колбэков вы получаете значение (или ошибку) через методы .then, .catch и .finally.
Три состояния #
| Состояние | Значение |
|---|---|
pending | операция ещё не завершена |
fulfilled | успех, есть результат |
rejected | сбой, есть причина (обычно Error) |
Переход из pending однократный: либо fulfilled, либо rejected, без возврата назад.
const pending = new Promise(() => {})
// pending остаётся pending, пока не вызовут resolve/reject
new Promise #
Низкоуровневое создание — функция-исполнитель с resolve и reject:
function requestInvoice(orderId) {
return new Promise((resolve, reject) => {
setTimeout(() => {
if (!orderId.startsWith('ord-')) {
reject(new Error('Неверный формат orderId'))
return
}
resolve({ orderId, total: 3290, currency: 'RUB' })
}, 400)
})
}
requestInvoice('ord-8841').then((invoice) => {
console.log(invoice.total) // 3290
})
Исполнитель запускается сразу при создании промиса. Тяжёлую синхронную работу внутри исполнителя избегайте — блокируете поток.
then, catch, finally #
requestInvoice('bad-id')
.then((invoice) => {
return invoice.total
})
.catch((err) => {
console.error(err.message) // "Неверный формат orderId"
return 0
})
.finally(() => {
console.log('Запрос счёта завершён')
})
.then(onFulfilled, onRejected?)— обработка успеха; необязательный второй аргумент — устаревший стиль, лучше.catch..catch(onRejected)— сокращение для.then(null, onRejected)..finally(onFinally)— код после завершения, без изменения переданного значения (кроме явногоthrow).
.then / .catch / .finally возвращают новый промис — основа цепочек (следующая статья).
Возврат значения и вложенный промис #
function loadCart() {
return Promise.resolve({ items: [{ sku: 'pen-01', qty: 2 }] })
}
loadCart()
.then((cart) => {
const count = cart.items.reduce((n, i) => n + i.qty, 0)
return count // оборачивается в Promise.resolve(count)
})
.then((count) => {
console.log(count) // 2
})
Если из .then вернуть другой промис, следующий .then дождётся его разрешения:
loadCart()
.then((cart) => requestInvoice(cart.orderId ?? 'ord-0000'))
.then((invoice) => console.log(invoice.total))
Promise.resolve и Promise.reject #
Готовые промисы для старта цепочки или тестов:
Promise.resolve(42).then((n) => n * 2) // 84
Promise.reject(new Error('Отмена оплаты')).catch((err) => {
console.log(err.message)
})
Синхронная функция, которая может бросить исключение, удобно оборачивается так:
function safeParse(json) {
return Promise.resolve().then(() => JSON.parse(json))
}
safeParse('{"ok":true}').then(console.log)
safeParse('broken').catch((err) => console.error(err.name)) // SyntaxError
От колбэка к промису #
Обёртка «промисификация» инкапсулирует соглашение (err, data):
function fetchTrackMetaAsync(trackId) {
return new Promise((resolve, reject) => {
fetchTrackMeta(trackId, (err, meta) => {
if (err) reject(err)
else resolve(meta)
})
})
}
// использование
fetchTrackMetaAsync('tr-01').then((meta) => console.log(meta.title))
Современные API ( fetch, fs.promises в Node) уже возвращают промисы — обёртки нужны для legacy-кода.
Итог #
- Промис фиксирует исход async-операции в одном из трёх состояний.
.then/.catch/.finallyподписываются на результат и строят цепочки.- Возврат значения или промиса из
.thenзадаёт вход для следующего звена. Promise.resolve/rejectи обёртки над колбэками упрощают миграцию старого кода.