Как правильно обрабатывать ошибки в JavaScript?
Вопрос
Как работает try/catch/finally, какие встроенные подклассы Error существуют, как создавать собственные классы ошибок и как ловить ошибки в асинхронном коде и отклонённых промисах?
Короткий ответ
try/catch/finally перехватывает синхронные исключения: catch выполняется при ошибке, finally — всегда, независимо от результата. Встроенные подклассы Error (TypeError, RangeError, ReferenceError и др.) описывают конкретные категории ошибок. Свои ошибки создают через class MyError extends Error. Асинхронные ошибки в async/await ловятся тем же try/catch, а непойманные отклонения промисов — через .catch() или глобальный обработчик unhandledrejection.
Подробный ответ
try/catch/finally. Блок try выполняет код, catch(error) перехватывает выброшенное исключение, finally выполняется в любом случае — даже если в try или catch есть return или новое исключение. finally полезен для гарантированной очистки ресурсов (закрытие соединений, снятие лоадеров). Важно: try/catch перехватывает только синхронные ошибки в своём блоке — ошибка, брошенная внутри setTimeout или в необработанном промисе внутри try, не будет поймана.
Объект Error и его подклассы. Каждая ошибка имеет message, name и stack (трассировку вызовов, не входит в спецификацию, но поддерживается везде). Встроенные подклассы:
TypeError— операция над значением неподходящего типа (null.foo, вызов не-функции).RangeError— числовое значение вне допустимого диапазона (new Array(-1),toFixed(101)).ReferenceError— обращение к необъявленной переменной.SyntaxError— некорректный синтаксис (обычно приeval/JSON.parse).URIError— ошибка в функцияхencodeURI/decodeURI.EvalError— устарел, практически не встречается.
Кастомные ошибки. Наследуются от Error через class, что даёт доступ к instanceof для различения типов ошибок в коде, обрабатывающем разные сценарии по-разному (валидация, сеть, авторизация и т.д.). Важно вызывать super(message) и явно задавать this.name, иначе name останется 'Error'.
Ошибки в асинхронном коде:
- В
async functionлюбое исключение (в том числеthrowи отклонённыйawait) превращается в отклонённый промис и ловится обычнымtry/catchвокругawait. - Промис без
awaitтребует.catch()в цепочке — иначе получаемunhandled promise rejection. Promise.allпадает целиком при первом отклонении — если нужно собрать результаты и ошибки отдельно, используютPromise.allSettled.- Глобально отловить пропущенные отклонения промисов можно через событие
window.addEventListener('unhandledrejection', ...)в браузере илиprocess.on('unhandledRejection', ...)в Node.js — полезно для логирования, но не заменяет локальную обработку.
Пример
// Базовый try/catch/finally
function parseConfig(json) {
try {
return JSON.parse(json)
} catch (error) {
console.error('Некорректный JSON:', error.message) // SyntaxError
return null
} finally {
console.log('Парсинг завершён') // выполнится в любом случае
}
}
// Встроенные подклассы Error
try {
null.foo() // TypeError: Cannot read properties of null
} catch (e) {
console.log(e instanceof TypeError, e.name) // true "TypeError"
}
// Кастомный класс ошибки
class ValidationError extends Error {
constructor(message, field) {
super(message)
this.name = 'ValidationError'
this.field = field
}
}
function validateAge(age) {
if (age < 0) {
throw new ValidationError('Возраст не может быть отрицательным', 'age')
}
return age
}
try {
validateAge(-5)
} catch (error) {
if (error instanceof ValidationError) {
console.log(`Ошибка поля "${error.field}": ${error.message}`)
} else {
throw error // не наша ошибка — пробрасываем дальше
}
}
// Асинхронные ошибки
async function fetchUser(id) {
try {
const response = await fetch(`/api/users/${id}`)
if (!response.ok) {
throw new Error(`HTTP ${response.status}`)
}
return await response.json()
} catch (error) {
console.error('Не удалось загрузить пользователя:', error.message)
return null
}
}
// Promise.allSettled — не падает при первой ошибке
const results = await Promise.allSettled([
fetchUser(1),
fetchUser(-1), // допустим, отклонится
])
results.forEach((r) =>
r.status === 'fulfilled' ? console.log(r.value) : console.log('Ошибка:', r.reason)
)
Дополнительные вопросы
- Чем
Promise.allотличается отPromise.allSettledиPromise.anyв обработке ошибок? - Как свойство
causeвnew Error(message, { cause })помогает при цепочке ошибок? - Почему
finallyсreturnвнутри может "перекрыть"return/throwизtry/catch?