Ты пишешь API-клиент для интернет-магазина: он принимает JSON от сервера, парсит его и достаёт цену товара. Что если JSON сломан? Что если поле цены отсутствует? Что если сервер вернул null?
Без обработки ошибок приложение просто упадёт — белый экран, потеря данных, расстроенный пользователь. try/catch позволяет перехватить ошибку, обработать её элегантно и продолжить работу.
throwtry {
// Код, который может бросить ошибку
const data = JSON.parse(userInput)
console.log(data.price)
} catch (error) {
// Выполняется только при ошибке в try
console.error('Не удалось разобрать данные:', error.message)
} finally {
// Выполняется ВСЕГДА — и при ошибке, и без неё
console.log('Попытка обработки завершена')
}try {
null.property // TypeError
} catch (e) {
console.log(e.name) // 'TypeError'
console.log(e.message) // "Cannot read properties of null (reading 'property')"
console.log(e.stack) // полный стек вызовов — полезно при отладке
}JSON.parse('{') — неверный синтаксисnull.foo, вызов не-функции — неверный типnew Array(-1) — значение вне диапазонаНе только JavaScript может бросать ошибки — ты тоже можешь:
function parseOrderAmount(value) {
const amount = Number(value)
if (isNaN(amount)) {
throw new TypeError(`Сумма заказа должна быть числом, получили: ${value}`)
}
if (amount <= 0) {
throw new RangeError(`Сумма заказа должна быть положительной, получили: ${amount}`)
}
return amount
}Бросать можно что угодно — строку, объект, число. Но всегда бросай объект Error (или его наследника): только у него есть удобный .stack.
Ловим только то, с чем умеем работать. Остальное — пробрасываем выше:
function loadConfig(raw) {
try {
return JSON.parse(raw)
} catch (e) {
if (e instanceof SyntaxError) {
// Знаем как обработать — логируем и возвращаем дефолт
console.warn('Конфиг сломан, используем дефолтный')
return {}
}
throw e // Неожиданная ошибка — пробрасываем выше
}
}async function processOrder(orderId) {
let dbConnection = null
try {
dbConnection = await openConnection()
const order = await dbConnection.query(`SELECT * FROM orders WHERE id = ${orderId}`)
return order
} catch (e) {
console.error('Ошибка БД:', e.message)
return null
} finally {
// Закроется в любом случае — и при успехе, и при ошибке
dbConnection?.close()
}
}1. Поглощение ошибок — молчаливый catch:
// Сломано — ошибка проглочена, невозможно отладить:
try {
riskyOperation()
} catch (e) {} // пустой catch — худшая практика!
// Исправлено — хотя бы логируй:
try {
riskyOperation()
} catch (e) {
console.error('Операция упала:', e.message)
// или throw e, если не знаешь как обработать
}2. try/catch не ловит асинхронные ошибки:
// Сломано — ошибка в setTimeout не поймается:
try {
setTimeout(() => {
throw new Error('Это не поймается!')
}, 100)
} catch (e) {
console.log('Не сработает') // не выполнится
}
// Исправлено — используй async/await:
try {
await asyncOperation()
} catch (e) {
console.error(e.message)
}3. Лишний try/catch вместо проверки данных:
// Сломано — try/catch не замена валидации:
function getPrice(product) {
try {
return product.price.toFixed(2)
} catch (e) {
return '0.00'
}
}
// Исправлено — проверяй данные явно:
function getPrice(product) {
if (!product || typeof product.price !== 'number') return '0.00'
return product.price.toFixed(2)
}(err, req, res, next)ErrorBoundary — компонент-«поймушка» для ошибок рендерингаПарсинг и валидация данных заказа с обработкой разных ошибок
function parseOrderData(json) {
// Шаг 1: парсинг JSON — может быть SyntaxError
let data
try {
data = JSON.parse(json)
} catch (e) {
throw new SyntaxError('Неверный формат данных заказа: ' + e.message)
}
// Шаг 2: валидация полей — бросаем TypeError при несоответствии типов
if (!data.productId || typeof data.productId !== 'number') {
throw new TypeError('Поле productId обязательно и должно быть числом')
}
if (!data.quantity || data.quantity <= 0) {
throw new RangeError('Количество товара должно быть положительным')
}
if (!data.customerEmail || !data.customerEmail.includes('@')) {
throw new TypeError('Поле customerEmail должно быть валидным email')
}
return {
productId: data.productId,
quantity: data.quantity,
email: data.customerEmail.toLowerCase(),
}
}
// Тестируем разные входные данные
const testCases = [
'{"productId":42,"quantity":2,"customerEmail":"ivan@shop.ru"}', // ОК
'{broken json', // SyntaxError
'{"productId":"abc","quantity":2,"customerEmail":"ok@ok.com"}', // TypeError
'{"productId":1,"quantity":-5,"customerEmail":"ok@ok.com"}', // RangeError
]
testCases.forEach((input, i) => {
try {
const order = parseOrderData(input)
console.log(`Тест ${i + 1} ОК: заказ #${order.productId} x${order.quantity}`)
} catch (e) {
if (e instanceof SyntaxError) {
console.log(`Тест ${i + 1} — Формат: ${e.message}`)
} else if (e instanceof TypeError) {
console.log(`Тест ${i + 1} — Тип данных: ${e.message}`)
} else if (e instanceof RangeError) {
console.log(`Тест ${i + 1} — Диапазон: ${e.message}`)
} else {
throw e // Неожиданная ошибка — пробрасываем
}
}
})
// Тест 1 ОК: заказ #42 x2
// Тест 2 — Формат: Неверный формат данных заказа: ...
// Тест 3 — Тип данных: Поле productId обязательно и должно быть числом
// Тест 4 — Диапазон: Количество товара должно быть положительнымТы пишешь API-клиент для интернет-магазина: он принимает JSON от сервера, парсит его и достаёт цену товара. Что если JSON сломан? Что если поле цены отсутствует? Что если сервер вернул null?
Без обработки ошибок приложение просто упадёт — белый экран, потеря данных, расстроенный пользователь. try/catch позволяет перехватить ошибку, обработать её элегантно и продолжить работу.
throwtry {
// Код, который может бросить ошибку
const data = JSON.parse(userInput)
console.log(data.price)
} catch (error) {
// Выполняется только при ошибке в try
console.error('Не удалось разобрать данные:', error.message)
} finally {
// Выполняется ВСЕГДА — и при ошибке, и без неё
console.log('Попытка обработки завершена')
}try {
null.property // TypeError
} catch (e) {
console.log(e.name) // 'TypeError'
console.log(e.message) // "Cannot read properties of null (reading 'property')"
console.log(e.stack) // полный стек вызовов — полезно при отладке
}JSON.parse('{') — неверный синтаксисnull.foo, вызов не-функции — неверный типnew Array(-1) — значение вне диапазонаНе только JavaScript может бросать ошибки — ты тоже можешь:
function parseOrderAmount(value) {
const amount = Number(value)
if (isNaN(amount)) {
throw new TypeError(`Сумма заказа должна быть числом, получили: ${value}`)
}
if (amount <= 0) {
throw new RangeError(`Сумма заказа должна быть положительной, получили: ${amount}`)
}
return amount
}Бросать можно что угодно — строку, объект, число. Но всегда бросай объект Error (или его наследника): только у него есть удобный .stack.
Ловим только то, с чем умеем работать. Остальное — пробрасываем выше:
function loadConfig(raw) {
try {
return JSON.parse(raw)
} catch (e) {
if (e instanceof SyntaxError) {
// Знаем как обработать — логируем и возвращаем дефолт
console.warn('Конфиг сломан, используем дефолтный')
return {}
}
throw e // Неожиданная ошибка — пробрасываем выше
}
}async function processOrder(orderId) {
let dbConnection = null
try {
dbConnection = await openConnection()
const order = await dbConnection.query(`SELECT * FROM orders WHERE id = ${orderId}`)
return order
} catch (e) {
console.error('Ошибка БД:', e.message)
return null
} finally {
// Закроется в любом случае — и при успехе, и при ошибке
dbConnection?.close()
}
}1. Поглощение ошибок — молчаливый catch:
// Сломано — ошибка проглочена, невозможно отладить:
try {
riskyOperation()
} catch (e) {} // пустой catch — худшая практика!
// Исправлено — хотя бы логируй:
try {
riskyOperation()
} catch (e) {
console.error('Операция упала:', e.message)
// или throw e, если не знаешь как обработать
}2. try/catch не ловит асинхронные ошибки:
// Сломано — ошибка в setTimeout не поймается:
try {
setTimeout(() => {
throw new Error('Это не поймается!')
}, 100)
} catch (e) {
console.log('Не сработает') // не выполнится
}
// Исправлено — используй async/await:
try {
await asyncOperation()
} catch (e) {
console.error(e.message)
}3. Лишний try/catch вместо проверки данных:
// Сломано — try/catch не замена валидации:
function getPrice(product) {
try {
return product.price.toFixed(2)
} catch (e) {
return '0.00'
}
}
// Исправлено — проверяй данные явно:
function getPrice(product) {
if (!product || typeof product.price !== 'number') return '0.00'
return product.price.toFixed(2)
}(err, req, res, next)ErrorBoundary — компонент-«поймушка» для ошибок рендерингаПарсинг и валидация данных заказа с обработкой разных ошибок
function parseOrderData(json) {
// Шаг 1: парсинг JSON — может быть SyntaxError
let data
try {
data = JSON.parse(json)
} catch (e) {
throw new SyntaxError('Неверный формат данных заказа: ' + e.message)
}
// Шаг 2: валидация полей — бросаем TypeError при несоответствии типов
if (!data.productId || typeof data.productId !== 'number') {
throw new TypeError('Поле productId обязательно и должно быть числом')
}
if (!data.quantity || data.quantity <= 0) {
throw new RangeError('Количество товара должно быть положительным')
}
if (!data.customerEmail || !data.customerEmail.includes('@')) {
throw new TypeError('Поле customerEmail должно быть валидным email')
}
return {
productId: data.productId,
quantity: data.quantity,
email: data.customerEmail.toLowerCase(),
}
}
// Тестируем разные входные данные
const testCases = [
'{"productId":42,"quantity":2,"customerEmail":"ivan@shop.ru"}', // ОК
'{broken json', // SyntaxError
'{"productId":"abc","quantity":2,"customerEmail":"ok@ok.com"}', // TypeError
'{"productId":1,"quantity":-5,"customerEmail":"ok@ok.com"}', // RangeError
]
testCases.forEach((input, i) => {
try {
const order = parseOrderData(input)
console.log(`Тест ${i + 1} ОК: заказ #${order.productId} x${order.quantity}`)
} catch (e) {
if (e instanceof SyntaxError) {
console.log(`Тест ${i + 1} — Формат: ${e.message}`)
} else if (e instanceof TypeError) {
console.log(`Тест ${i + 1} — Тип данных: ${e.message}`)
} else if (e instanceof RangeError) {
console.log(`Тест ${i + 1} — Диапазон: ${e.message}`)
} else {
throw e // Неожиданная ошибка — пробрасываем
}
}
})
// Тест 1 ОК: заказ #42 x2
// Тест 2 — Формат: Неверный формат данных заказа: ...
// Тест 3 — Тип данных: Поле productId обязательно и должно быть числом
// Тест 4 — Диапазон: Количество товара должно быть положительнымТы разрабатываешь сервис онлайн-банка. Напиши функцию `transfer(from, to, amount)`, которая переводит деньги между счетами. Функция должна бросать: - `TypeError` — если `amount` не является числом - `RangeError` — если `amount <= 0` или если на счёте `from` недостаточно средств - Возвращать объект `{ from, to, amount, newBalance }` при успехе Обработай все случаи в try/catch с разными сообщениями.
typeof amount !== "number" — для TypeError. amount <= 0 — для RangeError (нулевая/отрицательная сумма). from.balance < amount — для RangeError (недостаточно средств). В catch проверяй e instanceof TypeError и e instanceof RangeError.