W3docs

Пользовательские ошибки в 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"

Использование пользовательских ошибок

После определения пользовательской ошибки её можно выбрасывать в приложении так же, как любую стандартную ошибку:

javascript— editable

В этом примере адрес электронной почты проверяется по регулярному выражению. Если проверка не проходит, выбрасывается ValidationError. Эта ошибка перехватывается в блоке try...catch, и если она является экземпляром ValidationError, в лог записывается соответствующее сообщение.

Обработка нескольких типов пользовательских ошибок

В разных частях приложения может понадобиться создать различные типы ошибок. Вот как можно обрабатывать несколько пользовательских ошибок:

javascript— editable

Такой подход позволяет раздельно обрабатывать разные типы ошибок, делая приложение более надёжным и удобным для отладки.

По возможности предпочитайте instanceof сравнению строк error.name: instanceof также учитывает подклассы, поэтому лучше выдерживает рефакторинг.

Построение иерархии ошибок на основе базового класса

В реальных приложениях полезно дать всем ошибкам общего предка. Обработчик верхнего уровня сможет перехватывать все «ожидаемые» ошибки приложения в одном месте, а глубинный код по-прежнему сможет выбрасывать конкретные подтипы. Опция cause (ES2022) позволяет высокоуровневой ошибке обёртывать низкоуровневую, которая её вызвала, сохраняя исходную для целей отладки.

javascript— editable

Здесь this.name = this.constructor.name избавляет от необходимости повторять имя в каждом подклассе, а единственная проверка instanceof AppError охватывает NotFoundError, ConflictError и любые будущие подтипы.

Расширенная обработка пользовательских ошибок в JavaScript

Расширяя базовые знания, мы можем применять пользовательские ошибки в более сложных сценариях: асинхронных операциях и специфических случаях бизнес-логики.

Пример 1: Пользовательская обработка ошибок API

Этот пример демонстрирует, как создать и использовать пользовательскую ошибку для обработки проблем с запросами к API — например, когда запрашиваемый ресурс не найден или сервер возвращает ошибку. Поток async/await здесь сочетается с обработкой ошибок с промисами.

javascript— editable

Пояснение

  • Класс ApiError: Этот пользовательский класс фиксирует специфичные для API ошибки, сохраняя HTTP-код состояния вместе с пользовательским сообщением.
  • Функция fetchData: Пытается получить данные по указанному URL. Если ответ неуспешен, выбрасывается ApiError с подробным сообщением и кодом состояния.
  • Обработка ошибок: Ошибки перехватываются и обрабатываются соответствующим образом. Ошибки, связанные с API, логируются с подробной информацией; непредвиденные ошибки также перехватываются и фиксируются в логе.

Пример 2: Пользовательская обработка ошибок валидации

Используйте этот пример для обработки ошибок, связанных с валидацией данных, например при проверке пользовательского ввода.

javascript— editable

Пояснение

  • Класс 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 — мощная техника для более детальной обработки конкретных типов ошибок. Это повышает ясность и удобство сопровождения кода обработки ошибок, позволяя давать более точную обратную связь и предпринимать нужные действия в зависимости от условий ошибки. Данный подход не только помогает при отладке, но и повышает надёжность приложений, обеспечивая правильный перехват и обработку каждого типа ошибки.

Практика

Практика
В чём цель использования расширенных классов ошибок в JavaScript?
В чём цель использования расширенных классов ошибок в JavaScript?
Практика
Почему в конструкторе подкласса пользовательской ошибки super(message) должен вызываться до обращения к this?
Почему в конструкторе подкласса пользовательской ошибки super(message) должен вызываться до обращения к this?
Практика
Что позволяет делать опция cause из ES2022 при выбрасывании пользовательской ошибки?
Что позволяет делать опция cause из ES2022 при выбрасывании пользовательской ошибки?
Was this page helpful?