Мобильное приложение периодически теряет соединение. Запрос к API падает с ошибкой — и вместо красивого сообщения пользователь видит белый экран. Причина: промис отклонён, но .catch() нигде не добавлен. В Node.js это ещё хуже — необработанный rejection убивает весь процесс.
fetch('/api/user/1')
.then(res => res.json())
.then(user => renderProfile(user))
.catch(err => {
// Ловит ошибки из ЛЮБОГО места цепочки выше
console.error('Что-то пошло не так:', err.message)
showErrorBanner('Не удалось загрузить профиль')
})Если исключение брошено внутри .then(), оно автоматически попадает в следующий .catch():
Promise.resolve({ status: 500 })
.then(res => {
if (res.status !== 200) throw new Error('Сервер вернул ошибку')
return res.json()
})
.then(data => console.log(data)) // пропускается
.catch(err => console.error(err.message)) // 'Сервер вернул ошибку'getSettings()
.catch(err => {
console.warn('Не удалось загрузить настройки, используем дефолтные')
return { theme: 'light', language: 'ru' } // восстанавливаем значение
})
.then(settings => applySettings(settings)) // выполнится в любом случаеЕсли промис отклонён и нет .catch() — это необработанный отказ. В браузере это предупреждение в консоли, в Node.js — завершение процесса.
// Плохо: нет обработки ошибок
fetch('/api/data').then(r => r.json()) // UnhandledPromiseRejection!
// Хорошо:
fetch('/api/data')
.then(r => r.json())
.catch(err => handleError(err)) // всегда добавляй .catch()window.addEventListener('unhandledrejection', event => {
console.error('Необработанная ошибка промиса:', event.reason)
event.preventDefault() // предотвращает вывод в консоль
// отправить в систему мониторинга (Sentry, Datadog...)
logToMonitoring({ error: event.reason, url: location.href })
})В Node.js аналог: process.on('unhandledRejection', handler).
Сетевые запросы могут падать по временным причинам. Простой паттерн повтора:
function retry(fn, times, delay = 0) {
return fn().catch(err => {
if (times <= 0) return Promise.reject(err)
return new Promise(resolve => setTimeout(resolve, delay))
.then(() => retry(fn, times - 1, delay * 2)) // экспоненциальный backoff
})
}
// Повторить запрос до 3 раз с задержкой 100→200→400мс
retry(
() => fetch('/api/orders').then(r => r.json()),
3,
100
)
.then(orders => renderOrders(orders))
.catch(err => showError('Не удалось загрузить заказы после 3 попыток'))Каждая следующая попытка ждёт в 2 раза дольше, чтобы не перегружать сервер:
Promise.allSettled([
fetch('/api/user'),
fetch('/api/orders'),
fetch('/api/recommendations'),
])
.then(results => {
results.forEach((result, i) => {
if (result.status === 'fulfilled') {
console.log(`Запрос ${i} успешен`)
} else {
console.warn(`Запрос ${i} упал: ${result.reason.message}`)
}
})
})1. Потерянный catch — ошибка внутри .then() не выбрасывается дальше автоматически:
// Плохо: если renderProfile бросит ошибку — она нигде не поймается
fetch('/api/user')
.then(res => res.json())
.then(user => renderProfile(user))
// нет .catch() — UnhandledPromiseRejection!
// Хорошо: всегда добавляй .catch() в конец цепочки
fetch('/api/user')
.then(res => res.json())
.then(user => renderProfile(user))
.catch(err => showError(err.message))2. Ошибка в .catch() — нужен ещё один .catch() или она потеряется:
// Плохо: если handler в catch сам бросит ошибку — она потеряется
fetch('/api/user')
.catch(err => {
throw new Error('обёртка: ' + err.message) // эта ошибка никуда не дойдёт!
})
// Хорошо: добавь ещё один catch или логируй в finally
fetch('/api/user')
.catch(err => { throw new Error('обёртка: ' + err.message) })
.catch(err => console.error('Финальная ошибка:', err.message))3. Async/await без try/catch — то же самое что промис без .catch():
// Плохо: необработанная ошибка
async function loadUser() {
const res = await fetch('/api/user') // может упасть
return res.json()
}
// Хорошо: оборачивай await в try/catch
async function loadUser() {
try {
const res = await fetch('/api/user')
return res.json()
} catch (err) {
console.error('Не удалось загрузить пользователя:', err)
return null
}
}window.addEventListener('unhandledrejection') для логирования в Sentry.catch(() => defaultValue) вместо показа ошибки пользователюЦепочка промисов с восстановлением ошибки и retry-логикой
// Симуляция нестабильного API
let attemptCount = 0
function unstableApi(failTimes) {
return new Promise((resolve, reject) => {
setTimeout(() => {
attemptCount++
if (attemptCount <= failTimes) {
reject(new Error(`Попытка ${attemptCount}: сервер недоступен`))
} else {
resolve({ orders: [{ id: 1, total: 4500 }, { id: 2, total: 1200 }] })
}
}, 10)
})
}
// Восстановление в .catch()
Promise.resolve(null)
.then(() => { throw new Error('Настройки не найдены') })
.catch(err => {
console.warn(err.message + ', используем дефолтные')
return { theme: 'light', lang: 'ru' } // восстановление
})
.then(settings => {
console.log('Применяем настройки:', settings.theme)
})
// Retry с экспоненциальным backoff
function retry(fn, times, delay = 0) {
return fn().catch(err => {
console.log(err.message)
if (times <= 0) return Promise.reject(err)
return new Promise(r => setTimeout(r, delay))
.then(() => retry(fn, times - 1, delay === 0 ? 10 : delay * 2))
})
}
// Сброс счётчика и тест
attemptCount = 0
retry(() => unstableApi(2), 3)
.then(data => {
console.log('Успех после нескольких попыток!')
console.log('Заказов:', data.orders.length)
const total = data.orders.reduce((sum, o) => sum + o.total, 0)
console.log('Общая сумма:', total, '₽')
})
.catch(err => console.error('Все попытки исчерпаны:', err.message))Мобильное приложение периодически теряет соединение. Запрос к API падает с ошибкой — и вместо красивого сообщения пользователь видит белый экран. Причина: промис отклонён, но .catch() нигде не добавлен. В Node.js это ещё хуже — необработанный rejection убивает весь процесс.
fetch('/api/user/1')
.then(res => res.json())
.then(user => renderProfile(user))
.catch(err => {
// Ловит ошибки из ЛЮБОГО места цепочки выше
console.error('Что-то пошло не так:', err.message)
showErrorBanner('Не удалось загрузить профиль')
})Если исключение брошено внутри .then(), оно автоматически попадает в следующий .catch():
Promise.resolve({ status: 500 })
.then(res => {
if (res.status !== 200) throw new Error('Сервер вернул ошибку')
return res.json()
})
.then(data => console.log(data)) // пропускается
.catch(err => console.error(err.message)) // 'Сервер вернул ошибку'getSettings()
.catch(err => {
console.warn('Не удалось загрузить настройки, используем дефолтные')
return { theme: 'light', language: 'ru' } // восстанавливаем значение
})
.then(settings => applySettings(settings)) // выполнится в любом случаеЕсли промис отклонён и нет .catch() — это необработанный отказ. В браузере это предупреждение в консоли, в Node.js — завершение процесса.
// Плохо: нет обработки ошибок
fetch('/api/data').then(r => r.json()) // UnhandledPromiseRejection!
// Хорошо:
fetch('/api/data')
.then(r => r.json())
.catch(err => handleError(err)) // всегда добавляй .catch()window.addEventListener('unhandledrejection', event => {
console.error('Необработанная ошибка промиса:', event.reason)
event.preventDefault() // предотвращает вывод в консоль
// отправить в систему мониторинга (Sentry, Datadog...)
logToMonitoring({ error: event.reason, url: location.href })
})В Node.js аналог: process.on('unhandledRejection', handler).
Сетевые запросы могут падать по временным причинам. Простой паттерн повтора:
function retry(fn, times, delay = 0) {
return fn().catch(err => {
if (times <= 0) return Promise.reject(err)
return new Promise(resolve => setTimeout(resolve, delay))
.then(() => retry(fn, times - 1, delay * 2)) // экспоненциальный backoff
})
}
// Повторить запрос до 3 раз с задержкой 100→200→400мс
retry(
() => fetch('/api/orders').then(r => r.json()),
3,
100
)
.then(orders => renderOrders(orders))
.catch(err => showError('Не удалось загрузить заказы после 3 попыток'))Каждая следующая попытка ждёт в 2 раза дольше, чтобы не перегружать сервер:
Promise.allSettled([
fetch('/api/user'),
fetch('/api/orders'),
fetch('/api/recommendations'),
])
.then(results => {
results.forEach((result, i) => {
if (result.status === 'fulfilled') {
console.log(`Запрос ${i} успешен`)
} else {
console.warn(`Запрос ${i} упал: ${result.reason.message}`)
}
})
})1. Потерянный catch — ошибка внутри .then() не выбрасывается дальше автоматически:
// Плохо: если renderProfile бросит ошибку — она нигде не поймается
fetch('/api/user')
.then(res => res.json())
.then(user => renderProfile(user))
// нет .catch() — UnhandledPromiseRejection!
// Хорошо: всегда добавляй .catch() в конец цепочки
fetch('/api/user')
.then(res => res.json())
.then(user => renderProfile(user))
.catch(err => showError(err.message))2. Ошибка в .catch() — нужен ещё один .catch() или она потеряется:
// Плохо: если handler в catch сам бросит ошибку — она потеряется
fetch('/api/user')
.catch(err => {
throw new Error('обёртка: ' + err.message) // эта ошибка никуда не дойдёт!
})
// Хорошо: добавь ещё один catch или логируй в finally
fetch('/api/user')
.catch(err => { throw new Error('обёртка: ' + err.message) })
.catch(err => console.error('Финальная ошибка:', err.message))3. Async/await без try/catch — то же самое что промис без .catch():
// Плохо: необработанная ошибка
async function loadUser() {
const res = await fetch('/api/user') // может упасть
return res.json()
}
// Хорошо: оборачивай await в try/catch
async function loadUser() {
try {
const res = await fetch('/api/user')
return res.json()
} catch (err) {
console.error('Не удалось загрузить пользователя:', err)
return null
}
}window.addEventListener('unhandledrejection') для логирования в Sentry.catch(() => defaultValue) вместо показа ошибки пользователюЦепочка промисов с восстановлением ошибки и retry-логикой
// Симуляция нестабильного API
let attemptCount = 0
function unstableApi(failTimes) {
return new Promise((resolve, reject) => {
setTimeout(() => {
attemptCount++
if (attemptCount <= failTimes) {
reject(new Error(`Попытка ${attemptCount}: сервер недоступен`))
} else {
resolve({ orders: [{ id: 1, total: 4500 }, { id: 2, total: 1200 }] })
}
}, 10)
})
}
// Восстановление в .catch()
Promise.resolve(null)
.then(() => { throw new Error('Настройки не найдены') })
.catch(err => {
console.warn(err.message + ', используем дефолтные')
return { theme: 'light', lang: 'ru' } // восстановление
})
.then(settings => {
console.log('Применяем настройки:', settings.theme)
})
// Retry с экспоненциальным backoff
function retry(fn, times, delay = 0) {
return fn().catch(err => {
console.log(err.message)
if (times <= 0) return Promise.reject(err)
return new Promise(r => setTimeout(r, delay))
.then(() => retry(fn, times - 1, delay === 0 ? 10 : delay * 2))
})
}
// Сброс счётчика и тест
attemptCount = 0
retry(() => unstableApi(2), 3)
.then(data => {
console.log('Успех после нескольких попыток!')
console.log('Заказов:', data.orders.length)
const total = data.orders.reduce((sum, o) => sum + o.total, 0)
console.log('Общая сумма:', total, '₽')
})
.catch(err => console.error('Все попытки исчерпаны:', err.message))Напиши функцию retry(fn, times), которая вызывает fn() и при отказе повторяет попытку до times раз. Если все попытки исчерпаны — возвращает промис с последней ошибкой. Если хотя бы одна попытка успешна — возвращает её результат.
Проверь times <= 0 для выброса ошибки. Для рекурсии: return retry(fn, times - 1). Это вернёт новый промис следующей попытки.