JavaScript позволяет вызывать throw
с любыми аргументами, то есть технически наши классы ошибок не нуждаются в наследовании от Error
. Но если использовать наследование, то появляется возможность идентификации объектов ошибок посредством obj instanceof Error
. Так что лучше применять наследование.
По мере роста приложения, наши собственные ошибки образуют иерархию, например, HttpTimeoutError
может наследовать от HttpError
и так далее.
сс Error
встроенный, вот его примерный код, просто чтобы мы понимали, что расширяем:
// "Псевдокод" встроенного класса Error, определённого самим JavaScript
class Error {
constructor(message) {
this.message = message;
this.name = "Error"; // (разные имена для разных встроенных классов ошибок)
this.stack = <стек вызовов>; // нестандартное свойство, но обычно поддерживается } }`
Теперь давайте унаследуем от него ValidationError
и попробуем новый класс в действии:
_class ValidationError extends Error {_
constructor(message) {
super(message); // (1)
this.name = "ValidationError"; // (2) } }
function test() {
throw new ValidationError("Упс!"); }
try { test();
} catch(err) {
alert(err.message); // Упс!
alert(err.name); // ValidationError
alert(err.stack); // список вложенных вызовов с номерами строк для каждого }`
Обратите внимание: в строке (1)
вызываем родительский конструктор. JavaScript требует от нас вызова super
в дочернем конструкторе, так что это обязательно. Родительский конструктор устанавливает свойство message
.
Родительский конструктор также устанавливает свойство name
для "Error"
, поэтому в строке (2)
мы сбрасываем его на правильное значение.
Попробуем использовать его в readUser(json)
:
class ValidationError extends Error {
constructor(message) {
super(message);
this.name = "ValidationError"; } } // Использование function
readUser(json) {
let user = JSON.parse(json);
if (!user.age) {
throw new ValidationError("Нет поля: age"); }
if (!user.name) {
throw new ValidationError("Нет поля: name"); }
return user; } // Рабочий пример с try..catch
try {
let user = readUser('{ "age": 25 }');
} catch (err) {
if (err instanceof ValidationError) { _
alert("Некорректные данные: " + err.message); // Некорректные данные: Нет поля: name_
} else if (err instanceof SyntaxError) { // (*)
alert("JSON Ошибка Синтаксиса: " + err.message);
} else {
throw err; // неизвестная ошибка, пробросить исключение (**) } }`
Блок try..catch
в коде выше обрабатывает и нашу ValidationError
, и встроенную SyntaxError
из JSON.parse
Итого:
- Мы можем наследовать свои классы ошибок от
Error
и других встроенных классов ошибок, но нужно позаботиться о свойствеname
и не забыть вызватьsuper
. - Мы можем использовать
instanceof
для проверки типа ошибок. Это также работает с наследованием. Но иногда у нас объект ошибки, возникшей в сторонней библиотеке, и нет простого способа получить класс. Тогда для проверки типа ошибки можно использовать свойствоname
. - Обёртывание исключений является распространённой техникой: функция ловит низкоуровневые исключения и создаёт одно «высокоуровневое» исключение вместо разных низкоуровневых. Иногда низкоуровневые исключения становятся свойствами этого объекта, как
err.cause
в примерах выше, но это не обязательно.