Цепочки промисов и ошибки #
Цепочка промисов — последовательность вызовов .then (и при необходимости .catch / .finally), где каждый шаг получает результат предыдущего. Ошибка в любом звене «скатывается» вниз до ближайшего .catch, если её не обработали.
Линейный сценарий оформления заказа #
function validateCart(cart) {
if (!cart.items?.length) throw new Error('Пустая корзина')
return cart
}
function reserveItems(cart) {
return Promise.resolve({ ...cart, reserved: true })
}
function chargePayment(cart) {
return new Promise((resolve, reject) => {
setTimeout(() => {
if (cart.total > 50_000) reject(new Error('Нужно подтверждение банка'))
else resolve({ ...cart, paid: true })
}, 200)
})
}
function sendReceipt(order) {
return Promise.resolve({ ...order, receiptSent: true })
}
const cart = { items: [{ sku: 'bag-01' }], total: 1200 }
Promise.resolve(cart)
.then(validateCart)
.then(reserveItems)
.then(chargePayment)
.then(sendReceipt)
.then((order) => console.log('Готово:', order.receiptSent))
.catch((err) => console.error('Оформление сорвалось:', err.message))
Каждый .then возвращает новое значение или промис — цепочка остаётся плоской, без вложенности колбэков.
Как распространяется ошибка #
- Синхронный
throwвнутри.thenпревращает звено вrejected. - Возврат
Promise.reject(...)из.then— то же самое. - Необработанный сбой в исполнителе
new Promiseотклоняет промис.
Promise.resolve()
.then(() => {
throw new Error('Шаг 2')
})
.then(() => {
console.log('не выполнится')
})
.catch((err) => {
console.log(err.message) // "Шаг 2"
return 'восстановлено'
})
.then((value) => {
console.log(value) // "восстановлено"
})
После .catch, который вернул обычное значение, цепочка снова в состоянии fulfilled.
catch в середине цепочки #
Локальный .catch обрабатывает ошибку и может либо завершить сценарий, либо продолжить:
chargePayment(cart)
.catch((err) => {
if (err.message.includes('банка')) {
return { ...cart, paid: false, pendingBank: true }
}
throw err // другие ошибки — дальше по цепочке
})
.then((state) => {
if (state.pendingBank) {
console.log('Ожидаем 3-D Secure')
}
})
Различайте восстановление (вернули значение) и повторный проброс (throw err).
Вложенные then — антипаттерн #
Плохо — дублируется структура колбэков:
loadCart().then((cart) => {
return chargePayment(cart).then((paid) => {
return sendReceipt(paid).then((order) => order)
})
})
Лучше — последовательные .then или async/await:
loadCart()
.then(chargePayment)
.then(sendReceipt)
Если шаги независимы и могут идти параллельно, используйте Promise.all (отдельная статья), а не цепочку.
finally не меняет ветку #
chargePayment(cart)
.then((result) => result)
.finally(() => {
console.log('Снятие индикатора загрузки')
})
.then((result) => {
// result — то же, что вернул chargePayment (если не было throw в finally)
})
finally не принимает аргументов результата; если внутри finally бросить ошибку, последующие .then не получат исходное значение.
Необработанное отклонение #
Промис без .catch в конце цепочки при отклонении может привести к событию unhandledrejection (браузер / Node). В продакшене всегда завершайте пользовательские цепочки обработчиком или логированием.
doCheckout()
.then(showSuccess)
.catch((err) => {
showError(err.message)
reportToMonitoring(err)
})
Итог #
- Цепочка — плоская композиция шагов через возврат значений и промисов из
.then. - Ошибка прерывает промежуточные
.thenи попадает в ближайший.catch. - Локальный
.catchможет восстановить сценарий или пробросить ошибку дальше. - Избегайте вложенных
.then; для параллели —Promise.all, для линейной логики — цепочка илиasync/await.