Пользовательские ошибки в JavaScript
JavaScript позволяет создавать собственные типы ошибок, расширяя встроенный класс Error, что обеспечивает более детальную обработку ошибок
Пользовательские ошибки в JavaScript
JavaScript позволяет создавать собственные типы ошибок, расширяя встроенный класс Error. Пользовательская ошибка — это просто класс, наследующий от Error (или другого типа ошибки) и несущий осмысленное имя name плюс любые дополнительные данные, которые требуются в конкретной ситуации.
На этой странице рассматривается: зачем нужны пользовательские ошибки, как правильно их определять, как различать типы ошибок с помощью instanceof, как добавлять контекст (коды состояния, имена полей, исходный cause) и как организовать иерархию ошибок для реального приложения.
Зачем создавать пользовательские ошибки?
Выбрасывать обычный new Error("...") можно, но вызывающий код получает лишь строку для анализа. Пользовательские ошибки решают три задачи:
- Обработка по типу. С помощью
instanceof ValidationErrorможно реагировать на определённый вид сбоя, не разбирая текст сообщения — что хрупко и зависит от локали. - Дополнительный контекст. Пользовательский класс может хранить структурированные поля — HTTP
statusCode, проблемное полеfield, подсказку о повторной попытке — вместо того чтобы утрамбовывать всё в строку сообщения. - Чёткие иерархии. Общий базовый класс (например,
AppError) позволяет обработчику верхнего уровня перехватывать все ожидаемые ошибки приложения в одном месте, при этом внутренний код может выбрасывать конкретные подтипы.
Если вам нужно сначала изучить механику выбрасывания и перехвата ошибок, прочитайте Обработка ошибок: try...catch. Пользовательские ошибки строятся на наследовании классов и расширении встроенных классов.
Основы расширения класса Error
Чтобы создать пользовательскую ошибку, нужно расширить класс Error с помощью extend. Новый класс наследует message, stack и toString() из Error, и вы добавляете всё необходимое.
Создание пользовательского класса ошибки
Вот минимальный шаблон:
class ValidationError extends Error {
constructor(message) {
super(message); // Pass the message to the Error constructor (sets this.message)
this.name = "ValidationError"; // Override the default name "Error"
}
}Важны два момента:
super(message)должен вызываться первым. Внутри конструктора подкласса нельзя использоватьthisдо вызоваsuper(). КонструкторErrorустанавливаетthis.messageи захватывает трассировку стека.- Установите
this.name. Без этого ошибка будет отображаться как"Error"в сообщениях иtoString(). Заданиеnameделает логи и паттернswitch (error.name)читаемыми.
В некоторых примерах можно встретить Object.setPrototypeOf(this, new.target.prototype). С современными транспайлерами и нативными классами это обычно не требуется, но не вредит и гарантирует корректную работу instanceof даже при компиляции в ES5 или при использовании кода через границы окружений (iframe/worker). В примерах ниже это сохранено для надёжности.
После создания ошибка ведёт себя как любая другая:
const e = new ValidationError("bad input");
e.name; // "ValidationError"
e instanceof Error; // true — it is still a real Error
e.toString(); // "ValidationError: bad input"Использование пользовательских ошибок
После определения пользовательской ошибки её можно выбрасывать в приложении так же, как любую стандартную ошибку:
В этом примере адрес электронной почты проверяется по регулярному выражению. Если проверка не проходит, выбрасывается ValidationError. Эта ошибка перехватывается в блоке try...catch, и если она является экземпляром ValidationError, в лог записывается соответствующее сообщение.
Обработка нескольких типов пользовательских ошибок
В разных частях приложения может понадобиться создать различные типы ошибок. Вот как можно обрабатывать несколько пользовательских ошибок:
Такой подход позволяет раздельно обрабатывать разные типы ошибок, делая приложение более надёжным и удобным для отладки.
По возможности предпочитайте instanceof сравнению строк error.name: instanceof также учитывает подклассы, поэтому лучше выдерживает рефакторинг.
Построение иерархии ошибок на основе базового класса
В реальных приложениях полезно дать всем ошибкам общего предка. Обработчик верхнего уровня сможет перехватывать все «ожидаемые» ошибки приложения в одном месте, а глубинный код по-прежнему сможет выбрасывать конкретные подтипы. Опция cause (ES2022) позволяет высокоуровневой ошибке обёртывать низкоуровневую, которая её вызвала, сохраняя исходную для целей отладки.
Здесь this.name = this.constructor.name избавляет от необходимости повторять имя в каждом подклассе, а единственная проверка instanceof AppError охватывает NotFoundError, ConflictError и любые будущие подтипы.
Расширенная обработка пользовательских ошибок в JavaScript
Расширяя базовые знания, мы можем применять пользовательские ошибки в более сложных сценариях: асинхронных операциях и специфических случаях бизнес-логики.
Пример 1: Пользовательская обработка ошибок API
Этот пример демонстрирует, как создать и использовать пользовательскую ошибку для обработки проблем с запросами к API — например, когда запрашиваемый ресурс не найден или сервер возвращает ошибку. Поток async/await здесь сочетается с обработкой ошибок с промисами.
Пояснение
- Класс ApiError: Этот пользовательский класс фиксирует специфичные для API ошибки, сохраняя HTTP-код состояния вместе с пользовательским сообщением.
- Функция fetchData: Пытается получить данные по указанному URL. Если ответ неуспешен, выбрасывается
ApiErrorс подробным сообщением и кодом состояния. - Обработка ошибок: Ошибки перехватываются и обрабатываются соответствующим образом. Ошибки, связанные с API, логируются с подробной информацией; непредвиденные ошибки также перехватываются и фиксируются в логе.
Пример 2: Пользовательская обработка ошибок валидации
Используйте этот пример для обработки ошибок, связанных с валидацией данных, например при проверке пользовательского ввода.
Пояснение
- Класс ValidationError: Пользовательский класс ошибки, помогающий определить, какое именно поле входных данных не прошло проверку.
- Функция validateUser: Проверяет корректность данных пользователя. Если данные не соответствуют определённым критериям, выбрасывается
ValidationError. - Обработка ошибок: Перехватывает ошибки валидации и логирует их с подробной информацией. Другие типы ошибок также обрабатываются отдельно.
Лучшие практики и подводные камни
- Всегда вызывайте
super(message)первым, прежде чем обращаться кthis. - Устанавливайте
name, чтобы логи иtoString()были читаемы;this.name = this.constructor.nameделает это один раз для всей иерархии. - Предпочитайте
instanceofсравнениямerror.name— он также учитывает подклассы. - Перебрасывайте нераспознанные ошибки. Блок
catch, который поглощает всё (catch (e) {}), скрывает настоящие баги. Обрабатывайте известные типы и используйтеthrow errorдля остальных, как показано в примерах. - Используйте
causeдля обёртывания, а не замены. Когда вы перехватываете низкоуровневую ошибку и выбрасываете высокоуровневую, передавайте{ cause: original }, чтобы трассировка стека и первопричина сохранились. - Не используйте подклассы
Errorдля управления потоком выполнения. Ошибки предназначены для исключительных ситуаций, а не для обычного ветвления.
Заключение
Создание пользовательских классов ошибок в JavaScript путём расширения класса Error — мощная техника для более детальной обработки конкретных типов ошибок. Это повышает ясность и удобство сопровождения кода обработки ошибок, позволяя давать более точную обратную связь и предпринимать нужные действия в зависимости от условий ошибки. Данный подход не только помогает при отладке, но и повышает надёжность приложений, обеспечивая правильный перехват и обработку каждого типа ошибки.