Свои классы ошибок

ошибки≈ 7 минут

Здесь будет встроен видео-разбор темы (5–7 минут, YouTube / RuTube / VK Видео) — тем, кто лучше воспринимает на слух, текст ниже не обязателен.

Встроенных классов ошибок в JavaScript меньше десятка, а способов сломаться в вашей программе — сотни. Свой класс ошибки нужен, чтобы catch узнавал не «что-то упало», а «упало вот это, и вот подробности».

Разделы темы:·

Бросить в JavaScript можно что угодно — строку, число, объект. Язык не возражает. А вот тот, кто будет ловить, останется без половины информации.

js
Tab добавляет отступ. Чтобы выйти из редактора, нажмите Escape — фокус перейдёт на кнопку запуска.

У объектов Error есть message и stack, их понимают консоль, логгеры и отладчик. Поэтому свои ошибки делают не с нуля, а наследованием — class ... extends Error.

js
Tab добавляет отступ. Чтобы выйти из редактора, нажмите Escape — фокус перейдёт на кнопку запуска.

super(message) в конструкторе наследника обязателен: до его вызова this просто не существует. Заодно он и кладёт текст в message — вручную это делать не нужно.

Имя класса и свойство name — разные вещи. name живёт в прототипе Error, и наследование его не переписывает: класс называется по-своему, а ошибка представляется чужим именем.

js
Tab добавляет отступ. Чтобы выйти из редактора, нажмите Escape — фокус перейдёт на кнопку запуска.

Задать имя можно двумя способами: присвоить this.name в конструкторе или объявить поле класса. Поле короче, и конструктор тогда вообще не нужен — он пригодится, только когда в ошибку кладут что-то ещё.

js
Tab добавляет отступ. Чтобы выйти из редактора, нажмите Escape — фокус перейдёт на кнопку запуска.

Если у вас целое семейство ошибок, имя можно взять из new.target — это ссылка на тот класс, который реально вызвали через new. Один конструктор в предке обслуживает всех наследников.

js
Tab добавляет отступ. Чтобы выйти из редактора, нажмите Escape — фокус перейдёт на кнопку запуска.

Наследование даёт бесплатную иерархию: объект — экземпляр и своего класса, и всех предков сразу. Это позволяет ловить хоть одну конкретную ошибку, хоть целую ветку.

js
Tab добавляет отступ. Чтобы выйти из редактора, нажмите Escape — фокус перейдёт на кнопку запуска.

В catch прилетает одна переменная на все случаи жизни, и разбираться, что случилось, приходится внутри. instanceof — самый прямой способ: он смотрит на цепочку прототипов, а не на текст.

js
Tab добавляет отступ. Чтобы выйти из редактора, нажмите Escape — фокус перейдёт на кнопку запуска.

Не разбирайте message регулярками: сообщение пишут для человека, класс — для программы. Текст поправят при переводе, и проверка тихо перестанет срабатывать.

Часто низкоуровневая ошибка бесполезна снаружи: вызывающему коду важно «конфиг не прочитался», а не «неожиданный символ в позиции 1». Но выбрасывать подробности жалко — по ним чинят баг. Второй аргумент Error принимает объект с полем cause, куда кладут исходную ошибку.

js
Tab добавляет отступ. Чтобы выйти из редактора, нажмите Escape — фокус перейдёт на кнопку запуска.

Свои классы получают эту возможность даром: если собственного конструктора нет, работает конструктор Error, а он умеет принимать cause. Если конструктор всё же свой — не забудьте пробросить второй аргумент в super.

Обёртки складываются: каждый слой приложения добавляет свой смысл, а первопричина остаётся в самом низу. Дойти до неё — обычный цикл по cause.

js
Tab добавляет отступ. Чтобы выйти из редактора, нажмите Escape — фокус перейдёт на кнопку запуска.

Ошибка — не только сообщение. Ей почти всегда есть что рассказать: какой статус вернул сервер, какое поле формы не прошло проверку, сколько секунд ждать до повтора. Всё это кладут в поля объекта.

js
Tab добавляет отступ. Чтобы выйти из редактора, нажмите Escape — фокус перейдёт на кнопку запуска.

Альтернатива, к которой тянет новичка, — строковые коды вроде "BAD_SUM" и сравнение через ===. Работает, но плохо стареет: опечатка в строке не ошибка, а молчаливое «не совпало».

Что нужноСтроковый кодСвой класс
Отличить ошибку от ошибкисравнение строк, опечатка тихо даёт falseinstanceof, опечатка в имени класса — сразу ошибка
Передать подробностиполя приходится лепить рядом с кодомполя конструктора: status, field, retryAfter
Сгруппировать похожиесписок кодов или общий префиксобщий предок и один instanceof
Найти все места в кодепоиск по строкепоиск по классу, редактор подсказывает
js
Tab добавляет отступ. Чтобы выйти из редактора, нажмите Escape — фокус перейдёт на кнопку запуска.

  • Своя ошибка — это class X extends Error и обязательный super(message) в конструкторе наследника.
  • Свойство name не подхватывает имя класса: задайте его полем или в конструкторе, иначе ошибка представится как Error.
  • Различать ошибки надёжнее через instanceof, а не по тексту message; общий предок группирует целое семейство.
  • Второй аргумент { cause } сохраняет исходную ошибку внутри новой — обёртка не теряет первопричину.
  • Подробности ошибки живут в её полях (status, field), а не в строковом коде, который легко перепутать.

1/3Класс объявлен как class MyError extends Error {}. Что выведет console.log(new MyError("ой").name)?

2/3Как правильно передать исходную ошибку внутрь новой?

3/3Есть class AppError extends Error {} и class TimeoutError extends AppError {}. Что вернёт new TimeoutError() instanceof Error?

Семейство ошибок формы

Опишите базовый класс AppError и двух наследников: ValidationError с полем field и NotFoundError. Напишите функцию handle(e), которая возвращает "поле: " + e.field для ошибки валидации, "не найдено" — для второй и "неизвестно" — для всего остального.

js
Tab добавляет отступ. Чтобы выйти из редактора, нажмите Escape — фокус перейдёт на кнопку запуска.

Общий предок AppError сам по себе пустой — он нужен, чтобы позже одним instanceof AppError отделять «наши» ошибки от чужих TypeError и RangeError.

jsлистинг · не запускается
Листинг только для чтения: этот код не запускается в браузере.

Порядок проверок важен: наследники идут раньше предка. Если поставить instanceof AppError первым, он перехватит обе конкретные ошибки, и до них дело не дойдёт.

Не потерять первопричину

Напишите ConfigError и функцию loadSettings(text): она возвращает разобранный JSON, а при неудаче бросает ConfigError со своим сообщением и исходной ошибкой в cause. Проверьте, что e.name — ваш, а e.cause.name — SyntaxError.

Собственный конструктор здесь не нужен: раз его нет, работает унаследованный конструктор Error, а он уже умеет принимать вторым аргументом объект с cause.

jsлистинг · не запускается
Листинг только для чтения: этот код не запускается в браузере.

Если конструктор всё же понадобится (например, чтобы принять путь к файлу), не забудьте пробросить настройки дальше: constructor(path, options) { super("Не читается " + path, options); }.