Class validation error extends error

Современный учебник JavaScript. Contribute to javascript-tutorial/ru.javascript.info development by creating an account on GitHub.

Пользовательские ошибки, расширение Error

Когда что-то разрабатываем, то нам часто необходимы собственные классы ошибок для разных вещей, которые могут пойти не так в наших задачах. Для ошибок при работе с сетью может понадобиться HttpError, для операций с базой данных DbError, для поиска — NotFoundError и т.д.

Наши ошибки должны поддерживать базовые свойства, такие как message, name и, желательно, stack. Но также они могут иметь свои собственные свойства. Например, объекты HttpError могут иметь свойство statusCode со значениями 404, 403 или 500.

JavaScript позволяет вызывать throw с любыми аргументами, то есть технически наши классы ошибок не нуждаются в наследовании от Error. Но если использовать наследование, то появляется возможность идентификации объектов ошибок посредством obj instanceof Error. Так что лучше применять наследование.

По мере роста приложения, наши собственные ошибки образуют иерархию, например, HttpTimeoutError может наследовать от HttpError и так далее.

Расширение Error

В качестве примера рассмотрим функцию readUser(json), которая должна читать данные пользователя в формате JSON.

Пример того, как может выглядеть корректный json:

let json = `{ "name": "John", "age": 30 }`;

Внутри будем использовать JSON.parse. При получении некорректного json он будет генерировать ошибку SyntaxError. Но даже если json синтаксически верен, то это не значит, что это будет корректный пользователь, верно? Могут быть пропущены необходимые данные. Например, могут отсутствовать свойства nameи age, которые являются необходимыми для наших пользователей.

Наша функция readUser(json) будет не только читать JSON-данные, но и проверять их («валидировать»). Если необходимые поля отсутствуют или данные в неверном формате, то это будет ошибкой. Но не синтаксической ошибкой SyntaxError, потому что данные синтаксически корректны. Это будет другая ошибка.

Назовём её ошибкой валидации ValidationError и создадим для неё класс. Ошибка этого вида должна содержать информацию о поле, которое является источником ошибки.

Наш класс ValidationError должен наследовать от встроенного класса Error.

Класс 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.

Обратите внимание, как мы используем instanceof для проверки конкретного типа ошибки в строке (*).

Мы можем также проверить тип, используя err.name:

// ...
// вместо (err instanceof SyntaxError)
} else if (err.name == "SyntaxError") { // (*)
// ...

Версия с instanceof гораздо лучше, потому что в будущем мы собираемся расширить ValidationError, сделав его подтипы, такие как PropertyRequiredError. И проверка instanceof продолжит работать для новых наследованных классов. Так что это на будущее.

Также важно, что если catch встречает неизвестную ошибку, то он пробрасывает её в строке (**). Блокcatch знает, только как обрабатывать ошибки валидации и синтаксические ошибки, а другие виды ошибок (из-за опечаток в коде и другие непонятные) он должен выпустить наружу.

Дальнейшее наследование

Класс ValidationError является слишком общим. Много что может пойти не так. Свойство может отсутствовать или иметь неверный формат (например, строка как значение возраста age). Поэтому для отсутствующих свойств сделаем более конкретный класс PropertyRequiredError. Он будет нести дополнительную информацию о свойстве, которое отсутствует.

class ValidationError extends Error {
  constructor(message) {
    super(message);
    this.name = "ValidationError";
  }
}

*!*
class PropertyRequiredError extends ValidationError {
  constructor(property) {
    super("Нет свойства: " + property);
    this.name = "PropertyRequiredError";
    this.property = property;
  }
}
*/!*

// Применение
function readUser(json) {
  let user = JSON.parse(json);

  if (!user.age) {
    throw new PropertyRequiredError("age");
  }
  if (!user.name) {
    throw new PropertyRequiredError("name");
  }

  return user;
}

// Рабочий пример с try..catch

try {
  let user = readUser('{ "age": 25 }');
} catch (err) {
  if (err instanceof ValidationError) {
*!*
    alert("Неверные данные: " + err.message); // Неверные данные: Нет свойства: name
    alert(err.name); // PropertyRequiredError
    alert(err.property); // name
*/!*
  } else if (err instanceof SyntaxError) {
    alert("Ошибка синтаксиса JSON: " + err.message);
  } else {
    throw err; // неизвестная ошибка, повторно выбросит исключение
  }
}

Новый класс PropertyRequiredError очень просто использовать: необходимо указать только имя свойства new PropertyRequiredError(property). Сообщение для пользователя message генерируется конструктором.

Обратите внимание, что свойство this.name в конструкторе PropertyRequiredError снова присвоено вручную. Правда, немного утомительно — присваивать this.name = <class name> в каждом классе пользовательской ошибки. Можно этого избежать, если сделать наш собственный «базовый» класс ошибки, который будет ставить this.name = this.constructor.name. И затем наследовать все ошибки уже от него.

Давайте назовём его MyError.

Вот упрощённый код с MyError и другими пользовательскими классами ошибок:

class MyError extends Error {
  constructor(message) {
    super(message);
*!*
    this.name = this.constructor.name;
*/!*
  }
}

class ValidationError extends MyError { }

class PropertyRequiredError extends ValidationError {
  constructor(property) {
    super("Нет свойства: " + property);
    this.property = property;
  }
}

// name корректное
alert( new PropertyRequiredError("field").name ); // PropertyRequiredError

Теперь пользовательские ошибки стали намного короче, особенно ValidationError,
так как мы избавились от строки "this.name = ..." в конструкторе.

Обёртывание исключений

Назначение функции readUser в приведённом выше коде — это «чтение данных пользователя». В процессе могут возникнуть различные виды ошибок. Сейчас у нас есть SyntaxError и ValidationError, но в будущем функция readUser может расшириться и, возможно, генерировать другие виды ошибок.

Код, который вызывает readUser, должен обрабатывать эти ошибки.

Сейчас в нём используются проверки if в блоке catch, которые проверяют класс и обрабатывают известные ошибки и пробрасывают дальше неизвестные. Но если функция readUser генерирует несколько видов ошибок, то мы должны спросить себя: действительно ли мы хотим проверять все типы ошибок поодиночке во всех местах в коде, где вызывается readUser?

Часто ответ «Нет»: внешний код хочет быть на один уровень выше всего этого. Он хочет иметь какую-то обобщённую ошибку чтения данных. Почему именно это произошло — часто не имеет значения (об этом говорится в сообщении об ошибке). Или даже лучше, если есть способ получить подробности об ошибке, но только если нам это нужно.

Итак, давайте создадим новый класс ReadError для представления таких ошибок. Если ошибка возникает внутри readUser, мы её перехватим и сгенерируем ReadError. Мы также сохраним ссылку на исходную ошибку в свойстве cause. Тогда внешний код должен будет только проверить наличие ReadError.

Этот код определяет ошибку ReadError и демонстрирует её использование в readUserи try..catch:

class ReadError extends Error {
  constructor(message, cause) {
    super(message);
    this.cause = cause;
    this.name = 'ReadError';
  }
}

class ValidationError extends Error { /*...*/ }
class PropertyRequiredError extends ValidationError { /* ... */ }

function validateUser(user) {
  if (!user.age) {
    throw new PropertyRequiredError("age");
  }

  if (!user.name) {
    throw new PropertyRequiredError("name");
  }
}

function readUser(json) {
  let user;

  try {
    user = JSON.parse(json);
  } catch (err) {
*!*
    if (err instanceof SyntaxError) {
      throw new ReadError("Синтаксическая ошибка", err);
    } else {
      throw err;
    }
*/!*
  }

  try {
    validateUser(user);
  } catch (err) {
*!*
    if (err instanceof ValidationError) {
      throw new ReadError("Ошибка валидации", err);
    } else {
      throw err;
    }
*/!*
  }

}

try {
  readUser('{bad json}');
} catch (e) {
  if (e instanceof ReadError) {
*!*
    alert(e);
    // Исходная ошибка: SyntaxError:Unexpected token b in JSON at position 1
    alert("Исходная ошибка: " + e.cause);
*/!*
  } else {
    throw e;
  }
}

В приведённом выше коде readUser работает так, как описано — функция распознаёт синтаксические ошибки и ошибки валидации и выдаёт вместо них ошибки ReadError (неизвестные ошибки, как обычно, пробрасываются).

Внешний код проверяет только instanceof ReadError. Не нужно перечислять все возможные типы ошибок

Этот подход называется «обёртывание исключений», потому что мы берём «исключения низкого уровня» и «оборачиваем» их в ReadError, который является более абстрактным и более удобным для использования в вызывающем коде. Такой подход широко используется в объектно-ориентированном программировании.

Итого

  • Мы можем наследовать свои классы ошибок от Error и других встроенных классов ошибок, но нужно позаботиться о свойстве name и не забыть вызвать super.
  • Мы можем использовать instanceof для проверки типа ошибок. Это также работает с наследованием. Но иногда у нас объект ошибки, возникшей в сторонней библиотеке, и нет простого способа получить класс. Тогда для проверки типа ошибки можно использовать свойство name.
  • Обёртывание исключений является распространённой техникой: функция ловит низкоуровневые исключения и создаёт одно «высокоуровневое» исключение вместо разных низкоуровневых. Иногда низкоуровневые исключения становятся свойствами этого объекта, как err.cause в примерах выше, но это не обязательно.

In ES6:

class MyError extends Error {
  constructor(message) {
    super(message);
    this.name = 'MyError';
  }
}

source

Kostanos's user avatar

Kostanos

9,1564 gold badges50 silver badges62 bronze badges

answered Sep 23, 2015 at 22:51

Mohsen's user avatar

MohsenMohsen

63.6k33 gold badges158 silver badges184 bronze badges

12

The only standard field Error object has is the message property. (See MDN, or EcmaScript Language Specification, section 15.11) Everything else is platform specific.

Mosts environments set the stack property, but fileName and lineNumber are practically useless to be used in inheritance.

So, the minimalistic approach is:

function MyError(message) {
    this.name = 'MyError';
    this.message = message;
    this.stack = (new Error()).stack;
}
MyError.prototype = new Error;  // <-- remove this if you do not 
                                //     want MyError to be instanceof Error

You could sniff the stack, unshift unwanted elements from it and extract information like fileName and lineNumber, but doing so requires information about the platform JavaScript is currently running upon. Most cases that is unnecessary — and you can do it in post-mortem if you really want.

Safari is a notable exception. There is no stack property, but the throw keyword sets sourceURL and line properties of the object that is being thrown. Those things are guaranteed to be correct.

Test cases I used can be found here: JavaScript self-made Error object comparison.

robocat's user avatar

robocat

5,20545 silver badges64 bronze badges

answered Mar 9, 2011 at 20:05

Tero's user avatar

TeroTero

2,5412 gold badges13 silver badges2 bronze badges

10

In short:

  • If you are using ES6 without transpilers:

    class CustomError extends Error { /* ... */}
    
  • If you are using Babel transpiler:

Option 1: use babel-plugin-transform-builtin-extend

Option 2: do it yourself (inspired from that same library)

    function CustomError(...args) {
      const instance = Reflect.construct(Error, args);
      Reflect.setPrototypeOf(instance, Reflect.getPrototypeOf(this));
      return instance;
    }
    CustomError.prototype = Object.create(Error.prototype, {
      constructor: {
        value: Error,
        enumerable: false,
        writable: true,
        configurable: true
      }
    });
    Reflect.setPrototypeOf(CustomError, Error);
  • If you are using pure ES5:

    function CustomError(message, fileName, lineNumber) {
      var instance = new Error(message, fileName, lineNumber);
      Object.setPrototypeOf(instance, Object.getPrototypeOf(this));
      return instance;
    }
    CustomError.prototype = Object.create(Error.prototype, {
      constructor: {
        value: Error,
        enumerable: false,
        writable: true,
        configurable: true
      }
    });
    if (Object.setPrototypeOf){
        Object.setPrototypeOf(CustomError, Error);
    } else {
        CustomError.__proto__ = Error;
    }
    
  • Alternative: use Classtrophobic framework

Explanation:

Why extending the Error class using ES6 and Babel is a problem?

Because an instance of CustomError is not anymore recognized as such.

class CustomError extends Error {}
console.log(new CustomError('test') instanceof Error);// true
console.log(new CustomError('test') instanceof CustomError);// false

In fact, from the official documentation of Babel, you cannot extend any built-in JavaScript classes such as Date, Array, DOM or Error.

The issue is described here:

  • Native extends breaks HTMLELement, Array, and others
  • an object of The class which is extends by base type like Array,Number,Object,String or Error is not instanceof this class

What about the other SO answers?

All the given answers fix the instanceof issue but you lose the regular error console.log:

console.log(new CustomError('test'));
// output:
// CustomError {name: "MyError", message: "test", stack: "Error↵    at CustomError (<anonymous>:4:19)↵    at <anonymous>:1:5"}

Whereas using the method mentioned above, not only you fix the instanceof issue but you also keep the regular error console.log:

console.log(new CustomError('test'));
// output:
// Error: test
//     at CustomError (<anonymous>:2:32)
//     at <anonymous>:1:5

Indolering's user avatar

answered Apr 24, 2017 at 18:11

JBE's user avatar

JBEJBE

11.5k7 gold badges48 silver badges48 bronze badges

5

Edit: Please read comments. It turns out this only works well in V8 (Chrome / Node.JS) My intent was to provide a cross-browser solution, which would work in all browsers, and provide stack trace where support is there.

Edit: I made this Community Wiki to allow for more editing.

Solution for V8 (Chrome / Node.JS), works in Firefox, and can be modified to function mostly correctly in IE. (see end of post)

function UserError(message) {
  this.constructor.prototype.__proto__ = Error.prototype // Make this an instanceof Error.
  Error.call(this) // Does not seem necessary. Perhaps remove this line?
  Error.captureStackTrace(this, this.constructor) // Creates the this.stack getter
  this.name = this.constructor.name; // Used to cause messages like "UserError: message" instead of the default "Error: message"
  this.message = message; // Used to set the message
}

Original post on «Show me the code !»

Short version:

function UserError(message) {
  this.constructor.prototype.__proto__ = Error.prototype
  Error.captureStackTrace(this, this.constructor)
  this.name = this.constructor.name
  this.message = message
}

I keep this.constructor.prototype.__proto__ = Error.prototype inside the function to keep all the code together. But you can also replace this.constructor with UserError and that allows you to move the code to outside the function, so it only gets called once.

If you go that route, make sure you call that line before the first time you throw UserError.

That caveat does not apply the function, because functions are created first, no matter the order. Thus, you can move the function to the end of the file, without a problem.

Browser Compatibility

Works in Firefox and Chrome (and Node.JS) and fills all promises.

Internet Explorer fails in the following

  • Errors do not have err.stack to begin with, so «it’s not my fault».

  • Error.captureStackTrace(this, this.constructor) does not exist so you need to do something else like

    if(Error.captureStackTrace) // AKA if not IE
        Error.captureStackTrace(this, this.constructor)
    
  • toString ceases to exist when you subclass Error. So you also need to add.

    else
        this.toString = function () { return this.name + ': ' + this.message }
    
  • IE will not consider UserError to be an instanceof Error unless you run the following some time before you throw UserError

    UserError.prototype = Error.prototype
    

12

To avoid the boilerplate for every different type of error, I combined the wisdom of some of the solutions into a createErrorType function:

function createErrorType(name, init) {
  function E(message) {
    if (!Error.captureStackTrace)
      this.stack = (new Error()).stack;
    else
      Error.captureStackTrace(this, this.constructor);
    this.message = message;
    init && init.apply(this, arguments);
  }
  E.prototype = new Error();
  E.prototype.name = name;
  E.prototype.constructor = E;
  return E;
}

Then you can define new error types easily as follows:

var NameError = createErrorType('NameError', function (name, invalidChar) {
  this.message = 'The name ' + name + ' may not contain ' + invalidChar;
});

var UnboundError = createErrorType('UnboundError', function (variableName) {
  this.message = 'Variable ' + variableName + ' is not bound';
});

answered Jan 13, 2015 at 15:31

Ruben Verborgh's user avatar

Ruben VerborghRuben Verborgh

3,5252 gold badges31 silver badges43 bronze badges

2

In 2018, I think this is the best way; that supports IE9+ and modern browsers.

UPDATE: See this test and repo for comparison on different implementations.

function CustomError(message) {
    Object.defineProperty(this, 'name', {
        enumerable: false,
        writable: false,
        value: 'CustomError'
    });

    Object.defineProperty(this, 'message', {
        enumerable: false,
        writable: true,
        value: message
    });

    if (Error.hasOwnProperty('captureStackTrace')) { // V8
        Error.captureStackTrace(this, CustomError);
    } else {
        Object.defineProperty(this, 'stack', {
            enumerable: false,
            writable: false,
            value: (new Error(message)).stack
        });
    }
}

if (typeof Object.setPrototypeOf === 'function') {
    Object.setPrototypeOf(CustomError.prototype, Error.prototype);
} else {
    CustomError.prototype = Object.create(Error.prototype, {
        constructor: { value: CustomError }
    });
}

Also beware that __proto__ property is deprecated which is widely used in other answers.

answered Mar 9, 2016 at 1:52

Onur Yıldırım's user avatar

Onur YıldırımOnur Yıldırım

31.6k12 gold badges83 silver badges97 bronze badges

9

As some people have said, it’s fairly easy with ES6:

class CustomError extends Error { }

So I tried that within my app, (Angular, Typescript) and it just didn’t work. After some time I’ve found that the problem is coming from Typescript :O

See https://github.com/Microsoft/TypeScript/issues/13965

It’s very disturbing because if you do:

class CustomError extends Error {}
​

try {
  throw new CustomError()
} catch(e) {
  if (e instanceof CustomError) {
    console.log('Custom error');
  } else {
    console.log('Basic error');
  }
}

In node or directly into your browser it’ll display: Custom error

Try to run that with Typescript in your project on on Typescript playground, it’ll display Basic error

The solution is to do the following:

class CustomError extends Error {
  // we have to do the following because of: https://github.com/Microsoft/TypeScript/issues/13965
  // otherwise we cannot use instanceof later to catch a given type
  public __proto__: Error;

  constructor(message?: string) {
    const trueProto = new.target.prototype;
    super(message);

    this.__proto__ = trueProto;
  }
}

answered Feb 1, 2018 at 16:34

maxime1992's user avatar

maxime1992maxime1992

21.6k10 gold badges76 silver badges119 bronze badges

5

IMPORTANT: this answer is from 2016 and is now potentially outdated. JavaScript in general and Node.js in particular advanced quite a lot and now offer more syntax possibilities to achieve the same results. The content below is kept for historical reasons and just in case someone is dealing with legacy Node.js versions out there somewhere.


Original answer:

For the sake of completeness — just because none of the previous answers mentioned this method — if you are working with Node.js and don’t have to care about browser compatibility, the desired effect is pretty easy to achieve with the built in inherits of the util module (official docs here).

For example, let’s suppose you want to create a custom error class that takes an error code as the first argument and the error message as the second argument:

file custom-error.js:

'use strict';

var util = require('util');

function CustomError(code, message) {
  Error.captureStackTrace(this, CustomError);
  this.name = CustomError.name;
  this.code = code;
  this.message = message;
}

util.inherits(CustomError, Error);

module.exports = CustomError;

Now you can instantiate and pass/throw your CustomError:

var CustomError = require('./path/to/custom-error');

// pass as the first argument to your callback
callback(new CustomError(404, 'Not found!'));

// or, if you are working with try/catch, throw it
throw new CustomError(500, 'Server Error!');

Note that, with this snippet, the stack trace will have the correct file name and line, and the error instance will have the correct name!

This happens due to the usage of the captureStackTrace method, which creates a stack property on the target object (in this case, the CustomError being instantiated). For more details about how it works, check the documentation here.

answered Mar 13, 2016 at 4:37

Victor Schröder's user avatar

Victor SchröderVictor Schröder

6,2972 gold badges41 silver badges45 bronze badges

4

Crescent Fresh’s answer highly-voted answer is misleading. Though his warnings are invalid, there are other limitations that he doesn’t address.

First, the reasoning in Crescent’s «Caveats:» paragraph doesn’t make sense. The explanation implies that coding «a bunch of if (error instanceof MyError) else …» is somehow burdensome or verbose compared to multiple catch statements. Multiple instanceof statements in a single catch block are just as concise as multiple catch statements— clean and concise code without any tricks. This is a great way to emulate Java’s great throwable-subtype-specific error handling.

WRT «appears the message property of the subclass does not get set», that is not the case if you use a properly constructed Error subclass. To make your own ErrorX Error subclass, just copy the code block beginning with «var MyError =», changing the one word «MyError» to «ErrorX». (If you want to add custom methods to your subclass, follow the sample text).

The real and significant limitation of JavaScript error subclassing is that for JavaScript implementations or debuggers that track and report on stack trace and location-of-instantiation, like FireFox, a location in your own Error subclass implementation will be recorded as the instantiation point of the class, whereas if you used a direct Error, it would be the location where you ran «new Error(…)»). IE users would probably never notice, but users of Fire Bug on FF will see useless file name and line number values reported alongside these Errors, and will have to drill down on the stack trace to element #1 to find the real instantiation location.

answered Jul 23, 2010 at 14:20

Blaine's user avatar

BlaineBlaine

1,5582 gold badges16 silver badges15 bronze badges

3

How about this solution?

Instead of throwing your custom Error using:

throw new MyError("Oops!");

You would wrap the Error object (kind of like a Decorator):

throw new MyError(Error("Oops!"));

This makes sure all of the attributes are correct, such as the stack, fileName lineNumber, et cetera.

All you have to do then is either copy over the attributes, or define getters for them.
Here is an example using getters (IE9):

function MyError(wrapped)
{
        this.wrapped = wrapped;
        this.wrapped.name = 'MyError';
}

function wrap(attr)
{
        Object.defineProperty(MyError.prototype, attr, {
                get: function()
                {
                        return this.wrapped[attr];
                }
        });
}

MyError.prototype = Object.create(Error.prototype);
MyError.prototype.constructor = MyError;

wrap('name');
wrap('message');
wrap('stack');
wrap('fileName');
wrap('lineNumber');
wrap('columnNumber');

MyError.prototype.toString = function()
{
        return this.wrapped.toString();
};

answered Aug 16, 2014 at 22:35

JoWie's user avatar

JoWieJoWie

2513 silver badges3 bronze badges

3

My solution is more simple than the other answers provided and doesn’t have the downsides.

It preserves the Error prototype chain and all properties on Error without needing specific knowledge of them. It’s been tested in Chrome, Firefox, Node, and IE11.

The only limitation is an extra entry at the top of the call stack. But that is easily ignored.

Here’s an example with two custom parameters:

function CustomError(message, param1, param2) {
    var err = new Error(message);
    Object.setPrototypeOf(err, CustomError.prototype);

    err.param1 = param1;
    err.param2 = param2;

    return err;
}

CustomError.prototype = Object.create(
    Error.prototype,
    {name: {value: 'CustomError', enumerable: false}}
);

Example Usage:

try {
    throw new CustomError('Something Unexpected Happened!', 1234, 'neat');
} catch (ex) {
    console.log(ex.name); //CustomError
    console.log(ex.message); //Something Unexpected Happened!
    console.log(ex.param1); //1234
    console.log(ex.param2); //neat
    console.log(ex.stack); //stacktrace
    console.log(ex instanceof Error); //true
    console.log(ex instanceof CustomError); //true
}

For environments that require a polyfil of setPrototypeOf:

Object.setPrototypeOf = Object.setPrototypeOf || function (obj, proto) {
    obj.__proto__ = proto;
    return obj;
};

answered Jun 20, 2014 at 21:57

Ben's user avatar

BenBen

3,1251 gold badge26 silver badges32 bronze badges

3

On addition to standard message property, JavaScript now supports adding specific cause of the error as a optional param to the Error constructor:

const error1 = new Error('Error one');
const error2 = new Error('Error two', { cause: error1 });
// error2.cause === error1
  • Available in Node v16.9.0.
  • Available in Chrome, Firefox & Safari (see browser compatibility for versions)

answered Sep 28, 2021 at 16:51

t_dom93's user avatar

t_dom93t_dom93

9,2101 gold badge48 silver badges37 bronze badges

2

I didn’t like all the other answers, too long, too complicated or didn’t trace the stack correctly. Here my approach, if you need more custom props pass them to the constructor and set them like name.

class CustomError extends Error {
  constructor (message) {
    super(message)

    // needed for CustomError instanceof Error => true
    Object.setPrototypeOf(this, new.target.prototype);

    // Set the name
    this.name = this.constructor.name

    // Maintains proper stack trace for where our error was thrown (only available on V8)
    if (Error.captureStackTrace) {
      Error.captureStackTrace(this, this.constructor)
    }
  }
}

// create own CustomError sub classes
class SubCustomError extends CustomError{}

// Tests
console.log(new SubCustomError instanceof CustomError) // true
console.log(new SubCustomError instanceof CustomError) // true 
console.log(new CustomError instanceof Error) // true
console.log(new SubCustomError instanceof Error) // true

throw new SubCustomError ('test error')

answered Apr 23, 2021 at 13:20

SePeF's user avatar

SePeFSePeF

6257 silver badges13 bronze badges

3

In the above example Error.apply (also Error.call) doesn’t do anything for me (Firefox 3.6/Chrome 5). A workaround I use is:

function MyError(message, fileName, lineNumber) {
    var err = new Error();

    if (err.stack) {
        // remove one stack level:
        if (typeof(Components) != 'undefined') {
            // Mozilla:
            this.stack = err.stack.substring(err.stack.indexOf('n')+1);
        }
        else if (typeof(chrome) != 'undefined' || typeof(process) != 'undefined') {
            // Google Chrome/Node.js:
            this.stack = err.stack.replace(/n[^n]*/,'');
        }
        else {
            this.stack = err.stack;
        }
    }
    this.message    = message    === undefined ? err.message    : message;
    this.fileName   = fileName   === undefined ? err.fileName   : fileName;
    this.lineNumber = lineNumber === undefined ? err.lineNumber : lineNumber;
}

MyError.prototype = new Error();
MyError.prototype.constructor = MyError;
MyError.prototype.name = 'MyError';

answered Aug 21, 2010 at 0:36

panzi's user avatar

panzipanzi

7,4535 gold badges41 silver badges51 bronze badges

0

In Node as others have said, it’s simple:

class DumbError extends Error {
    constructor(foo = 'bar', ...params) {
        super(...params);

        if (Error.captureStackTrace) {
            Error.captureStackTrace(this, DumbError);
        }

        this.name = 'DumbError';

        this.foo = foo;
        this.date = new Date();
    }
}

try {
    let x = 3;
    if (x < 10) {
        throw new DumbError();
    }
} catch (error) {
    console.log(error);
}

answered Jun 23, 2019 at 15:19

Muhammad Umer's user avatar

Muhammad UmerMuhammad Umer

16.8k19 gold badges91 silver badges162 bronze badges

1

This isn’t that complicated, but I personally find it the easiest way to extend an error easily.

export default class ExtendableError extends Error {
    constructor(message) {
        super(message);
        this.name = this.constructor.name;
    }
}

Create a utility class like so called ExtendableError. The purpose of this utility class is to be like the normal Error class, but change the name property to the name of the class by default, so it’s really easy to extend an error.

Now, if you want to extend an error, it only takes one line.

class MyError extends ExtendableError {}

answered Aug 13, 2021 at 16:55

Corman's user avatar

CormanCorman

7199 silver badges15 bronze badges

1

I just want to add to what others have already stated:

To make sure that the custom error class shows up properly in the stack trace, you need to set the custom error class’s prototype’s name property to the custom error class’s name property.
This is what I mean:

CustomError.prototype = Error.prototype;
CustomError.prototype.name = 'CustomError';

So the full example would be:

    var CustomError = function(message) {
        var err = new Error(message);
        err.name = 'CustomError';
        this.name = err.name;
        this.message = err.message;
        //check if there is a stack property supported in browser
        if (err.stack) {
            this.stack = err.stack;
        }
        //we should define how our toString function works as this will be used internally
        //by the browser's stack trace generation function
        this.toString = function() {
           return this.name + ': ' + this.message;
        };
    };
    CustomError.prototype = new Error();
    CustomError.prototype.name = 'CustomError';

When all is said and done, you throw your new exception and it looks like this (I lazily tried this in the chrome dev tools):

CustomError: Stuff Happened. GASP!
    at Error.CustomError (<anonymous>:3:19)
    at <anonymous>:2:7
    at Object.InjectedScript._evaluateOn (<anonymous>:603:39)
    at Object.InjectedScript._evaluateAndWrap (<anonymous>:562:52)
    at Object.InjectedScript.evaluate (<anonymous>:481:21)

answered Feb 11, 2014 at 4:41

Gautham C.'s user avatar

Gautham C.Gautham C.

1,09711 silver badges12 bronze badges

2

My 2 cents:

Why another answer?

a) Because accessing the Error.stack property (as in some answers) have a large performance penalty.

b) Because it is only one line.

c) Because the solution at https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Error does not seems to preserve stack info.

//MyError class constructor
function MyError(msg){
    this.__proto__.__proto__ = Error.apply(null, arguments);
};

usage example

http://jsfiddle.net/luciotato/xXyeB/

What does it do?

this.__proto__.__proto__ is MyError.prototype.__proto__, so it is setting the __proto__ FOR ALL INSTANCES
of MyError to a specific newly created Error. It keeps MyError class properties and methods and also puts the new Error properties (including .stack) in the __proto__ chain.

Obvious problem:

You can not have more than one instance of MyError with useful stack info.

Do not use this solution if you do not fully understand what this.__proto__.__proto__= does.

Community's user avatar

answered Jun 29, 2014 at 21:40

Lucio M. Tato's user avatar

Lucio M. TatoLucio M. Tato

5,5392 gold badges30 silver badges30 bronze badges

Since JavaScript Exceptions are difficult to sub-class, I don’t sub-class. I just create a new Exception class and use an Error inside of it. I change the Error.name property so that it looks like my custom exception on the console:

var InvalidInputError = function(message) {
    var error = new Error(message);
    error.name = 'InvalidInputError';
    return error;
};

The above new exception can be thrown just like a regular Error and it will work as expected, for example:

throw new InvalidInputError("Input must be a string");
// Output: Uncaught InvalidInputError: Input must be a string 

Caveat: the stack trace is not perfect, as it will bring you to where the new Error is created and not where you throw. This is not a big deal on Chrome because it provides you with a full stack trace directly in the console. But it’s more problematic on Firefox, for example.

answered May 2, 2014 at 20:44

Jonathan Benn's user avatar

Jonathan BennJonathan Benn

2,5603 gold badges22 silver badges27 bronze badges

6

As pointed out in Mohsen’s answer, in ES6 it’s possible to extend errors using classes. It’s a lot easier and their behavior is more consistent with native errors…but unfortunately it’s not a simple matter to use this in the browser if you need to support pre-ES6 browsers. See below for some notes on how that might be implemented, but in the meantime I suggest a relatively simple approach that incorporates some of the best suggestions from other answers:

function CustomError(message) {
    //This is for future compatibility with the ES6 version, which
    //would display a similar message if invoked without the
    //`new` operator.
    if (!(this instanceof CustomError)) {
        throw new TypeError("Constructor 'CustomError' cannot be invoked without 'new'");
    }
    this.message = message;

    //Stack trace in V8
    if (Error.captureStackTrace) {
       Error.captureStackTrace(this, CustomError);
    }
    else this.stack = (new Error).stack;
}
CustomError.prototype = Object.create(Error.prototype);
CustomError.prototype.name = 'CustomError';

In ES6 it’s as simple as:

class CustomError extends Error {}

…and you can detect support for ES6 classes with try {eval('class X{}'), but you’ll get a syntax error if you attempt to include the ES6 version in a script that’s loaded by older browsers. So the only way to support all browsers would be to load a separate script dynamically (e.g. via AJAX or eval()) for browsers that support ES6. A further complication is that eval() isn’t supported in all environments (due to Content Security Policies), which may or may not be a consideration for your project.

So for now, either the first approach above or simply using Error directly without trying to extend it seems to be the best that can practically be done for code that needs to support non-ES6 browsers.

There is one other approach that some people might want to consider, which is to use Object.setPrototypeOf() where available to create an error object that’s an instance of your custom error type but which looks and behaves more like a native error in the console (thanks to Ben’s answer for the recommendation). Here’s my take on that approach: https://gist.github.com/mbrowne/fe45db61cea7858d11be933a998926a8. But given that one day we’ll be able to just use ES6, personally I’m not sure the complexity of that approach is worth it.

Community's user avatar

answered Dec 27, 2016 at 3:18

Matt Browne's user avatar

Matt BrowneMatt Browne

12k4 gold badges56 silver badges73 bronze badges

Mohsen has a great answer above in ES6 that sets the name, but if you’re using TypeScript or if you’re living in the future where hopefully this proposal for public and private class fields has moved past stage 3 as a proposal and made it into stage 4 as part of ECMAScript/JavaScript then you might want to know this is then just a little bit shorter. Stage 3 is where browsers start implementing features, so if your browser supports it the code below just might work. (Tested in the new Edge browser v81 it seems to work fine). Be warned though this is an unstable feature at the moment and should be used cautiously and you should always check browser support on unstable features. This post is mainly for those future dwellers when browsers might support this. To check support check MDN and Can I use. It’s currently got 66% support across the browser market which is getting there but not that great so if you really want to use it now and don’t want to wait either use a transpiler like Babel or something like TypeScript.

class EOFError extends Error { 
  name="EOFError"
}
throw new EOFError("Oops errored");

Compare this to a nameless error which when thrown will not log it’s name.

class NamelessEOFError extends Error {}
throw new NamelessEOFError("Oops errored");

answered May 17, 2020 at 6:50

John's user avatar

JohnJohn

6,9502 gold badges37 silver badges57 bronze badges

The way to do this right is to return the result of the apply from the constructor, as well as setting the prototype in the usual complicated javascripty way:

function MyError() {
    var tmp = Error.apply(this, arguments);
    tmp.name = this.name = 'MyError'

    this.stack = tmp.stack
    this.message = tmp.message

    return this
}
    var IntermediateInheritor = function() {}
        IntermediateInheritor.prototype = Error.prototype;
    MyError.prototype = new IntermediateInheritor()

var myError = new MyError("message");
console.log("The message is: '"+myError.message+"'") // The message is: 'message'
console.log(myError instanceof Error)                // true
console.log(myError instanceof MyError)              // true
console.log(myError.toString())                      // MyError: message
console.log(myError.stack)                           // MyError: message n 
                                                     // <stack trace ...>

The only problems with this way of doing it at this point (i’ve iterated it a bit) are that

  • properties other than stack and message aren’t included in MyError and
  • the stacktrace has an additional line that isn’t really necessary.

The first problem could be fixed by iterating through all the non-enumerable properties of error using the trick in this answer: Is it possible to get the non-enumerable inherited property names of an object?, but this isn’t supported by ie<9. The second problem could be solved by tearing out that line in the stack trace, but I’m not sure how to safely do that (maybe just removing the second line of e.stack.toString() ??).

Community's user avatar

answered Aug 31, 2013 at 19:32

B T's user avatar

B TB T

55.4k34 gold badges184 silver badges202 bronze badges

1

The snippet shows it all.

function add(x, y) {
      if (x && y) {
        return x + y;
      } else {
        /**
         * 
         * the error thrown will be instanceof Error class and InvalidArgsError also
         */
        throw new InvalidArgsError();
        // throw new Invalid_Args_Error(); 
      }
    }

    // Declare custom error using using Class
    class Invalid_Args_Error extends Error {
      constructor() {
        super("Invalid arguments");
        Error.captureStackTrace(this);
      }
    }

    // Declare custom error using Function
    function InvalidArgsError(message) {
      this.message = `Invalid arguments`;
      Error.captureStackTrace(this);
    }
    // does the same magic as extends keyword
    Object.setPrototypeOf(InvalidArgsError.prototype, Error.prototype);

    try{
      add(2)
    }catch(e){
      // true
      if(e instanceof Error){
        console.log(e)
      }
      // true
      if(e instanceof InvalidArgsError){
        console.log(e)
      }
    }

answered Dec 2, 2018 at 8:31

Amarpreet Singh's user avatar

My proposed solution is to use the .name property of error to distinguish between error types instead of instancof

This doesn’t exactly answer the question, but I think makes for a reasonable solution, for some scenarios anyway.

The benefit I’ve seen of being able to have an instanceof CustomError is that you can do custom handling in your promise catch handler.

For example:

class CustomError extends Error {/** ... **/}

axios
  .post(url, payload)
  .then(data => {
    if (!data.loggedIn) throw CustomError("not logged in");
    return data;
  })
  .catch(error => {
    if (error instanceof CustomError) {/** custom handling of error*//}
    throw error
  })

If that’s what you’re trying to accomplish, you be be well suited by the .name parameter as-well though:

export const ERROR_NOT_LOGGED_IN = "ERROR_NOT_LOGGED_IN";

axios
  .post(url, payload)
  .then(data => {
    if (!data.loggedIn) throw Error("not logged in").name=ERROR_NOT_LOGGED_IN ;
    return data;
  })
  .catch(error => {
    if (error.name === ERROR_NOT_LOGGED_IN) {/** custom handling of error*//}
    throw error
  })

answered Jun 7, 2021 at 17:04

Daniel's user avatar

DanielDaniel

32.7k17 gold badges94 silver badges145 bronze badges

I would take a step back and consider why you want to do that? I think the point is to deal with different errors differently.

For example, in Python, you can restrict the catch statement to only catch MyValidationError, and perhaps you want to be able to do something similar in javascript.

catch (MyValidationError e) {
    ....
}

You can’t do this in javascript. There’s only going to be one catch block. You’re supposed to use an if statement on the error to determine its type.


catch(e) {
if(isMyValidationError(e)) {
...
} else {
// maybe rethrow?
throw e;
}
}

I think I would instead throw a raw object with a type, message, and any other properties you see fit.

throw { type: "validation", message: "Invalid timestamp" }

And when you catch the error:

catch(e) {
    if(e.type === "validation") {
         // handle error
    }
    // re-throw, or whatever else
}

answered Dec 13, 2014 at 4:13

hasen's user avatar

hasenhasen

159k64 gold badges190 silver badges229 bronze badges

1

Custom Error Decorator

This is based on George Bailey’s answer, but extends and simplifies the original idea. It is written in CoffeeScript, but is easy to convert to JavaScript. The idea is extend Bailey’s custom error with a decorator that wraps it, allowing you to create new custom errors easily.

Note: This will only work in V8. There is no support for Error.captureStackTrace in other environments.

Define

The decorator takes a name for the error type, and returns a function that takes an error message, and encloses the error name.

CoreError = (@message) ->

    @constructor.prototype.__proto__ = Error.prototype
    Error.captureStackTrace @, @constructor
    @name = @constructor.name

BaseError = (type) ->

    (message) -> new CoreError "#{ type }Error: #{ message }"

Use

Now it is simple to create new error types.

StorageError   = BaseError "Storage"
SignatureError = BaseError "Signature"

For fun, you could now define a function that throws a SignatureError if it is called with too many args.

f = -> throw SignatureError "too many args" if arguments.length

This has been tested pretty well and seems to work perfectly on V8, maintaing the traceback, position etc.

Note: Using new is optional when constructing a custom error.

Community's user avatar

answered Mar 11, 2015 at 20:41

Carl Smith's user avatar

Carl SmithCarl Smith

3,00523 silver badges36 bronze badges

if you don’t care about performances for errors this is the smallest you can do

Object.setPrototypeOf(MyError.prototype, Error.prototype)
function MyError(message) {
    const error = new Error(message)
    Object.setPrototypeOf(error, MyError.prototype);
    return error
}

you can use it without new just MyError(message)

By changing the prototype after the constructor Error is called we don’t have to set the callstack and message

answered Dec 5, 2019 at 2:10

bormat's user avatar

bormatbormat

1,29112 silver badges16 bronze badges

In the course of developing something, it is necessary to have own error classes for reflecting particular things that can go wrong. For errors in network operations, HttpError is necessary, for searching operations-NotFoundError, for database operations- DbError, and more. They should support basic error properties such as name, message, and stack. Also, they can include other properties. For example, HttpError objects can contain a statusCode such as 403, 404, or 500.

JavaScript allows using throw with any argument. So, technically, custom errors don’t need inheritance from Error.

Now, let’s discover what extending error is. To be more precise, we can start from a function readUser(json), which reads JSON with user data.

So, a valid json looks like this:

let json = `{ "name": "John", "age": 25 }`;

Internally, JSON.parse is used.

In case it gets malformed json then it throws SyntaxError. Even if json is syntactically correct it doesn’t consider that the user is valid. It can miss essential data such as name, age, or other properties.

The readUser(json) function both reads JSON and checks the data. In case the format is wrong, then an error occurs. As the data is syntactically correct, it’s not a SyntaxError. It is a ValidationError.

For ValidationError it is necessary to inherit from the built-in Error class.

Here is the code for extending:

// "Pseudocode" for the built-in Error class 
class Error {
  constructor(message) {
    this.message = message;
    this.errorName = "Error"; // different names for different built-in error classes
    this.stack = < call stack > ; // non-standard, but most environments support it
  }
}

In the example below, ValidationError is inherited from it:

w3docs logo
Javascript ValidationError class

class ValidationError extends Error {
constructor(message) {
super(message);
this.name = «ValidationError»;
}
}
function testFunc() {
throw new ValidationError(«Oops»);
}
try {
testFunc();
} catch (err) {
console.log(err.message); // Oops
console.log(err.name); // ValidationError
console.log(err.stack); // a list of nested calls, with line numbers for each
}

Now, let’s try using it in createUser(json), like this:

w3docs logo
Javascript ValidationError class

class ValidationError extends Error {
constructor(message) {
super(message);
this.name = «ValidationError»;
}
}
function createUser(json) {
let user = JSON.parse(json);
if (!user.age) {
throw new ValidationError(«No field: age»);
}
if (!user.name) {
throw new ValidationError(«No field: name»);
}
return user;
}
try {
let user = createUser(‘{ «age»: 20 }’);
} catch (err) {
if (err instanceof ValidationError) {
console.log(«Invalid data: » + err.message); // Invalid data: No field: name
} else if (err instanceof SyntaxError) {//(*)
console.log(«JSON Syntax Error: » + err.message);
} else {
throw err; // unknown error, rethrow it
}
}

So, in the code above the try..catch block handles ValidationError and SyntaxError from JSON.parse.

It is especially interesting how instanceof is used for checking a particular error in the (*) line.

The err.name can also look like here:

// ...
// instead of, err instanceof SyntaxError
} else if (err.name == "SyntaxError") { // (*)
}

However, the version with instanceof is better, as the next step is to extend ValidationError, making subtypes of it. The instanceof check will keep working for new inheriting classes.

The ValidationError class is very universal. Hence, various things can go wrong. For example, a property can be in a wrong format or it can be absent.
Let’s consider the PropertyRequiredError for absent properties. It carries extra information about the missing property. Here is how it looks like:

w3docs logo
Javascript ValidationError and PropertyRequiredError class

class ValidationError extends Error {
constructor(message) {
super(message);
this.name = «ValidationError»;
}
}
class PropertyRequiredError extends ValidationError {
constructor(property) {
super(«Not a property: » + property);
this.name = «PropertyRequiredError»;
this.property = property;
}
}
function createUser(json) {
let user = JSON.parse(json);
if (!user.age) {
throw new PropertyRequiredError(«age»);
}
if (!user.name) {
throw new PropertyRequiredError(«name»);
}
return user;
}
// Example with try..catch
try {
let user = createUser(‘{ «age»: 20 }’);
} catch (err) {
if (err instanceof ValidationError) {
console.log(«Invalid data: » + err.message); // Invalid data: No property: name
console.log(err.name); // PropertyRequiredError
console.log(err.property); // name
} else if (err instanceof SyntaxError) {
console.log(«JSON Syntax Error: » + err.message);
} else {
throw err; // unknown error, rethrow it
}
}

The new class PropertyRequiredError is easy in usage. All you need is to do is passing the property name: new PropertyRequiredError(property). The constructor creates a human-readable message.

Please, consider that this.name in PropertyRequiredError constructor is assigned manually. So, assigning this.name = <class name> in each custom error class. For avoiding that the basic error class, which assigns this.name = this.constructor.name is made. It can inherit all the custom errors. It can be called ErrorName.

Here it is and other custom error classes:

w3docs logo
Javascript ValidationError and PropertyRequiredError class

class ErrorName extends Error {
constructor(message) {
super(message);
this.name = this.constructor.name;
}
}
class ValidationError extends ErrorName {}
class PropertyRequiredError extends ValidationError {
constructor(property) {
super(«Not a property: » + property);
this.property = property;
}
}
// correct name
console.log(new PropertyRequiredError(«field»).name); // PropertyRequiredError

So, now custom errors are shorter.

The purpose of the createUser function is reading the user data. In the process, various errors may occur.

The code calling createUser must handle the errors. In the example below, it applies multiple ifs in the catch block, checking the class and handling known errors and rethrowing the unknown ones.

The scheme will look as follows:

In the code above, there are two kinds of errors. Of course, they can be more.
The technique, represented in this part is known as “wrapping extensions”.
Let’s start at making a new class ReadError to present a generic data reading error. The readUser function catches the data reading errors, occurring inside it (for example, ValidationError and SyntaxError) and create ReadError uthuinstead.

The ReadError object keeps the reference to the original error inside the cause property.

try {
  //...
  createUser() // the potential error source
  //...
}
catch (err) {
  if (err instanceof ValidationError) {
    // handle validation error
  } else if (err instanceof SyntaxError) {
    // handle syntax error
  } else {
    throw err; // unknown error, rethrow it
  }
}

Let’s take a look at the code, which specifies ReadError demonstrating its usage in readUser and try..catch:

w3docs logo
Javascript try…catch error handling

class ReadError extends Error {
constructor(message, cause) {
super(message);
this.cause = cause;
this.name = ‘ReadError’;
}
}
class ValidationError extends Error { /*…*/ }
class PropertyRequiredError extends ValidationError { /* … */ }
function validateUser(user) {
if (!user.age) {
throw new PropertyRequiredError(«age»);
}
if (!user.name) {
throw new PropertyRequiredError(«name»);
}
}
function createUser(json) {
let user;
try {
user = JSON.parse(json);
} catch (err) {
if (err instanceof SyntaxError) {
throw new ReadError(«Syntax Error», err);
} else {
throw err;
}
}
try {
validateUser(user);
} catch (err) {
if (err instanceof ValidationError) {
throw new ReadError(«Validation Error», err);
} else {
throw err;
}
}
}
try {
createUser(‘{bad json}’);
} catch (e) {
if (e instanceof ReadError) {
console.log(e);
// Error: SyntaxError:
// Unexpected token b in JSON at position 1
console.log(«Error: » + e.cause);
} else {
throw e;
}
}

The approach, described above is named “wrapping exceptions” as “low-level” exceptions are taken and wrapped into ReadError, which is more abstract.

In brief, we can state that, normally, it is possible to inherit from Error and other built-in classes. All you need is to take care of the name property, not forgetting to call super.

The instanceof class can also be applied to check for specific errors. It also operates with inheritance. At times when an error object comes from a third-party library, the name property can be used.

A widespread and useful technique is wrapping exceptions. With it, a function can handle low-level exceptions, creating high-level errors, instead of different low-level ones.

When we develop something, we often need our own error classes to reflect specific things that may go wrong in our tasks. For errors in network operations we may need HttpError, for database operations DbError, for searching operations NotFoundError and so on.

Our errors should support basic error properties like message, name and, preferably, stack. But they also may have other properties of their own, e.g. HttpError objects may have statusCode property with a value like 404 or 403 or 500.

JavaScript allows to use throw with any argument, so technically our custom error classes don’t need to inherit from Error. But if we inherit, then it becomes possible to use obj instanceof Error to identify error objects. So it’s better to inherit from it.

As we build our application, our own errors naturally form a hierarchy, for instance HttpTimeoutError may inherit from HttpError, and so on.

Extending Error

As an example, let’s consider a function readUser(json) that should read JSON with user data.

Here’s an example of how a valid json may look:

let json = `{ "name": "John", "age": 30 }`;

Internally, we’ll use JSON.parse. If it receives malformed json, then it throws SyntaxError.

But even if json is syntactically correct, that doesn’t mean that it’s a valid user, right? It may miss the necessary data. For instance, if may not have name and age properties that are essential for our users.

Our function readUser(json) will not only read JSON, but check («validate») the data. If there are no required fields, or the format is wrong, then that’s an error. And that’s not a SyntaxError, because the data is syntactically correct, but another kind of error. We’ll call it ValidationError and create a class for it. An error of that kind should also carry the information about the offending field.

Our ValidationError class should inherit from the built-in Error class.

That class is built-in, but we should have its approximate code before our eyes, to understand what we’re extending.

So here you are:

// The "pseudocode" for the built-in Error class defined by JavaScript itself
class Error {
  constructor(message) {
    this.message = message;
    this.name = "Error"; // (different names for different built-in error classes)
    this.stack = <nested calls>; // non-standard, but most environments support it
  }
}

Now let’s go on and inherit ValidationError from it:

*!*
class ValidationError extends Error {
*/!*
  constructor(message) {
    super(message); // (1)
    this.name = "ValidationError"; // (2)
  }
}

function test() {
  throw new ValidationError("Whoops!");
}

try {
  test();
} catch(err) {
  alert(err.message); // Whoops!
  alert(err.name); // ValidationError
  alert(err.stack); // a list of nested calls with line numbers for each
}

Please take a look at the constructor:

  1. In the line (1) we call the parent constructor. JavaScript requires us to call super in the child constructor, so that’s obligatory. The parent constructor sets the message property.
  2. The parent constructor also sets the name property to "Error", so in the line (2) we reset it to the right value.

Let’s try to use it in readUser(json):

class ValidationError extends Error {
  constructor(message) {
    super(message);
    this.name = "ValidationError";
  }
}

// Usage
function readUser(json) {
  let user = JSON.parse(json);

  if (!user.age) {
    throw new ValidationError("No field: age");
  }
  if (!user.name) {
    throw new ValidationError("No field: name");
  }

  return user;
}

// Working example with try..catch

try {
  let user = readUser('{ "age": 25 }');
} catch (err) {
  if (err instanceof ValidationError) {
*!*
    alert("Invalid data: " + err.message); // Invalid data: No field: name
*/!*
  } else if (err instanceof SyntaxError) { // (*)
    alert("JSON Syntax Error: " + err.message);
  } else {
    throw err; // unknown error, rethrow it (**)
  }
}

The try..catch block in the code above handles both our ValidationError and the built-in SyntaxError from JSON.parse.

Please take a look at how we use instanceof to check for the specific error type in the line (*).

We could also look at err.name, like this:

// ...
// instead of (err instanceof SyntaxError)
} else if (err.name == "SyntaxError") { // (*)
// ...

The instanceof version is much better, because in the future we are going to extend ValidationError, make subtypes of it, like PropertyRequiredError. And instanceof check will continue to work for new inheriting classes. So that’s future-proof.

Also it’s important that if catch meets an unknown error, then it rethrows it in the line (**). The catch only knows how to handle validation and syntax errors, other kinds (due to a typo in the code or such) should fall through.

Further inheritance

The ValidationError class is very generic. Many things may go wrong. The property may be absent or it may be in a wrong format (like a string value for age). Let’s make a more concrete class PropertyRequiredError, exactly for absent properties. It will carry additional information about the property that’s missing.

class ValidationError extends Error {
  constructor(message) {
    super(message);
    this.name = "ValidationError";
  }
}

*!*
class PropertyRequiredError extends ValidationError {
  constructor(property) {
    super("No property: " + property);
    this.name = "PropertyRequiredError";
    this.property = property;
  }
}
*/!*

// Usage
function readUser(json) {
  let user = JSON.parse(json);

  if (!user.age) {
    throw new PropertyRequiredError("age");
  }
  if (!user.name) {
    throw new PropertyRequiredError("name");
  }

  return user;
}

// Working example with try..catch

try {
  let user = readUser('{ "age": 25 }');
} catch (err) {
  if (err instanceof ValidationError) {
*!*
    alert("Invalid data: " + err.message); // Invalid data: No property: name
    alert(err.name); // PropertyRequiredError
    alert(err.property); // name
*/!*
  } else if (err instanceof SyntaxError) {
    alert("JSON Syntax Error: " + err.message);
  } else {
    throw err; // unknown error, rethrow it
  }
}

The new class PropertyRequiredError is easy to use: we only need to pass the property name: new PropertyRequiredError(property). The human-readable message is generated by the constructor.

Please note that this.name in PropertyRequiredError constructor is again assigned manually. That may become a bit tedius — to assign this.name = <class name> when creating each custom error. But there’s a way out. We can make our own «basic error» class that removes this burden from our shoulders by using this.constructor.name for this.name in the constructor. And then inherit from it.

Let’s call it MyError.

Here’s the code with MyError and other custom error classes, simplified:

class MyError extends Error {
  constructor(message) {
    super(message);
*!*
    this.name = this.constructor.name;
*/!*
  }
}

class ValidationError extends MyError { }

class PropertyRequiredError extends ValidationError {
  constructor(property) {
    super("No property: " + property);
    this.property = property;
  }
}

// name is correct
alert( new PropertyRequiredError("field").name ); // PropertyRequiredError

Now custom errors are much shorter, especially ValidationError, as we got rid of the "this.name = ..." line in the constructor.

Wrapping exceptions

The purpose of the function readUser in the code above is «to read the user data», right? There may occur different kinds of errors in the process. Right now we have SyntaxError and ValidationError, but in the future readUser function may grow: the new code will probably generate other kinds of errors.

The code which calls readUser should handle these errors. Right now it uses multiple if in the catch block to check for different error types and rethrow the unknown ones. But if readUser function generates several kinds of errors — then we should ask ourselves: do we really want to check for all error types one-by-one in every code that calls readUser?

Often the answer is «No»: the outer code wants to be «one level above all that». It wants to have some kind of «data reading error». Why exactly it happened — is often irrelevant (the error message describes it). Or, even better if there is a way to get error details, but only if we need to.

So let’s make a new class ReadError to represent such errors. If an error occurs inside readUser, we’ll catch it there and generate ReadError. We’ll also keep the reference to the original error in its cause property. Then the outer code will only have to check for ReadError.

Here’s the code that defines ReadError and demonstrates its use in readUser and try..catch:

class ReadError extends Error {
  constructor(message, cause) {
    super(message);
    this.cause = cause;
    this.name = 'ReadError';
  }
}

class ValidationError extends Error { /*...*/ }
class PropertyRequiredError extends ValidationError { /* ... */ }

function validateUser(user) {
  if (!user.age) {
    throw new PropertyRequiredError("age");
  }

  if (!user.name) {
    throw new PropertyRequiredError("name");
  }
}

function readUser(json) {
  let user;

  try {
    user = JSON.parse(json);
  } catch (err) {
*!*
    if (err instanceof SyntaxError) {
      throw new ReadError("Syntax Error", err);
    } else {
      throw err;
    }
*/!*
  }

  try {
    validateUser(user);
  } catch (err) {
*!*
    if (err instanceof ValidationError) {
      throw new ReadError("Validation Error", err);
    } else {
      throw err;
    }
*/!*
  }

}

try {
  readUser('{bad json}');
} catch (e) {
  if (e instanceof ReadError) {
*!*
    alert(e);
    // Original error: SyntaxError: Unexpected token b in JSON at position 1
    alert("Original error: " + e.cause);
*/!*
  } else {
    throw e;
  }
}

In the code above, readUser works exactly as described — catches syntax and validation errors and throws ReadError errors instead (unknown errors are rethrown as usual).

So the outer code checks instanceof ReadError and that’s it. No need to list possible all error types.

The approach is called «wrapping exceptions», because we take «low level exceptions» and «wrap» them into ReadError that is more abstract and more convenient to use for the calling code. It is widely used in object-oriented programming.

Summary

  • We can inherit from Error and other built-in error classes normally, just need to take care of name property and don’t forget to call super.
  • Most of the time, we should use instanceof to check for particular errors. It also works with inheritance. But sometimes we have an error object coming from the 3rd-party library and there’s no easy way to get the class. Then name property can be used for such checks.
  • Wrapping exceptions is a widespread technique when a function handles low-level exceptions and makes a higher-level object to report about the errors. Low-level exceptions sometimes become properties of that object like err.cause in the examples above, but that’s not strictly required.

Понравилась статья? Поделить с друзьями:
  • Class name error python
  • Class key error
  • Class is public should be declared in a file named java как исправить
  • Class error not found joomla 4
  • Clash of clans как изменить почту