Свои классы ошибок
Здесь будет встроен видео-разбор темы (5–7 минут, YouTube / RuTube / VK Видео) — тем, кто лучше воспринимает на слух, текст ниже не обязателен.
Встроенных классов ошибок в JavaScript меньше десятка, а способов сломаться в вашей программе — сотни. Свой класс ошибки нужен, чтобы catch узнавал не «что-то упало», а «упало вот это, и вот подробности».
Бросить в JavaScript можно что угодно — строку, число, объект. Язык не возражает. А вот тот, кто будет ловить, останется без половины информации.
У объектов Error есть message и stack, их понимают консоль, логгеры и отладчик. Поэтому свои ошибки делают не с нуля, а наследованием — class ... extends Error.
super(message) в конструкторе наследника обязателен: до его вызова this просто не существует. Заодно он и кладёт текст в message — вручную это делать не нужно.
Имя класса и свойство name — разные вещи. name живёт в прототипе Error, и наследование его не переписывает: класс называется по-своему, а ошибка представляется чужим именем.
Задать имя можно двумя способами: присвоить this.name в конструкторе или объявить поле класса. Поле короче, и конструктор тогда вообще не нужен — он пригодится, только когда в ошибку кладут что-то ещё.
Если у вас целое семейство ошибок, имя можно взять из new.target — это ссылка на тот класс, который реально вызвали через new. Один конструктор в предке обслуживает всех наследников.
Наследование даёт бесплатную иерархию: объект — экземпляр и своего класса, и всех предков сразу. Это позволяет ловить хоть одну конкретную ошибку, хоть целую ветку.
В catch прилетает одна переменная на все случаи жизни, и разбираться, что случилось, приходится внутри. instanceof — самый прямой способ: он смотрит на цепочку прототипов, а не на текст.
Не разбирайте message регулярками: сообщение пишут для человека, класс — для программы. Текст поправят при переводе, и проверка тихо перестанет срабатывать.
Часто низкоуровневая ошибка бесполезна снаружи: вызывающему коду важно «конфиг не прочитался», а не «неожиданный символ в позиции 1». Но выбрасывать подробности жалко — по ним чинят баг. Второй аргумент Error принимает объект с полем cause, куда кладут исходную ошибку.
Свои классы получают эту возможность даром: если собственного конструктора нет, работает конструктор Error, а он умеет принимать cause. Если конструктор всё же свой — не забудьте пробросить второй аргумент в super.
Обёртки складываются: каждый слой приложения добавляет свой смысл, а первопричина остаётся в самом низу. Дойти до неё — обычный цикл по cause.
Ошибка — не только сообщение. Ей почти всегда есть что рассказать: какой статус вернул сервер, какое поле формы не прошло проверку, сколько секунд ждать до повтора. Всё это кладут в поля объекта.
Альтернатива, к которой тянет новичка, — строковые коды вроде "BAD_SUM" и сравнение через ===. Работает, но плохо стареет: опечатка в строке не ошибка, а молчаливое «не совпало».
| Что нужно | Строковый код | Свой класс |
|---|---|---|
| Отличить ошибку от ошибки | сравнение строк, опечатка тихо даёт false | instanceof, опечатка в имени класса — сразу ошибка |
| Передать подробности | поля приходится лепить рядом с кодом | поля конструктора: status, field, retryAfter |
| Сгруппировать похожие | список кодов или общий префикс | общий предок и один instanceof |
| Найти все места в коде | поиск по строке | поиск по классу, редактор подсказывает |
- Своя ошибка — это
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 для ошибки валидации, "не найдено" — для второй и "неизвестно" — для всего остального.
Общий предок AppError сам по себе пустой — он нужен, чтобы позже одним instanceof AppError отделять «наши» ошибки от чужих TypeError и RangeError.
Порядок проверок важен: наследники идут раньше предка. Если поставить instanceof AppError первым, он перехватит обе конкретные ошибки, и до них дело не дойдёт.
Не потерять первопричину
Напишите ConfigError и функцию loadSettings(text): она возвращает разобранный JSON, а при неудаче бросает ConfigError со своим сообщением и исходной ошибкой в cause. Проверьте, что e.name — ваш, а e.cause.name — SyntaxError.
Собственный конструктор здесь не нужен: раз его нет, работает унаследованный конструктор Error, а он уже умеет принимать вторым аргументом объект с cause.
Если конструктор всё же понадобится (например, чтобы принять путь к файлу), не забудьте пробросить настройки дальше: constructor(path, options) { super("Не читается " + path, options); }.