244

Can I define custom types for user-defined exceptions in JavaScript? If so, how would I do it?

Pang
  • 9,564
  • 146
  • 81
  • 122
Manki
  • 3,779
  • 4
  • 25
  • 18
  • 3
    Beware. According to [JavaScript in 10 Minutes](https://github.com/spencertipping/js-in-ten-minutes) you won't get a stack trace if you throw an unboxed value. – Janus Troelsen Dec 20 '11 at 03:13
  • https://exceptionsjs.com provides the ability to create custom exceptions and provides some missing exceptions including ArgumentException and NotImplemented by default. – Steven Wexler Aug 04 '14 at 02:37

13 Answers13

237

From WebReference:

throw { 
  name:        "System Error", 
  level:       "Show Stopper", 
  message:     "Error detected. Please contact the system administrator.", 
  htmlMessage: "Error detected. Please contact the <a href=\"mailto:sysadmin@acme-widgets.com\">system administrator</a>.",
  toString:    function(){return this.name + ": " + this.message;} 
}; 
Ivan
  • 34,531
  • 8
  • 55
  • 100
jon077
  • 10,303
  • 11
  • 39
  • 37
  • 7
    @b.long It's in "JavaScript: The Good Parts" (great book IMO). This Google Books preview shows the section: http://books.google.com/books?id=PXa2bby0oQ0C&pg=PA32&lpg=PA32 – orip Feb 14 '13 at 08:24
  • 12
    Adding a toString method will make it show nicely in the javascript console. without it shows like: Uncaught # with the toString it shows like: Uncaught System Error: Error detected. Please contact the system administrator. – JDC Aug 16 '13 at 07:06
  • 13
    This will not allow you stack traces unless you inherit from Error – Luke H Jul 30 '14 at 19:53
  • How can you filter within a catch block to only work with this custom error ? – Overdrivr Mar 24 '20 at 07:57
  • @overdrivr something like ⦃ `catch (e) { if (e instanceof TypeError) { … } else { throw e; } }` ⦄ or ⦃ `catch (e) { switch (e.constructor) { case TypeError: …; break; default: throw e; }` ⦄. – sam boosalis Apr 05 '20 at 06:14
  • @samboosalis this works if you're throwing a TypeError, which is not the case here (and even if it were, I want to be able to filter exactly this error and not catch other manually-created errors). – Overdrivr Apr 05 '20 at 07:08
  • 2
    This does not explain how to create a user-defined exception. -1 because there is only code and no explanation. Are these fields mandatory? Are these fields optional? Does the `toString()` function have a specific meaning for some browsers or some other JavaScript functions? etc. – Suzanne Soy Jun 23 '21 at 19:46
  • Can anyone explain why this is the top answer? It seems like inheriting from Error is the normal way to do this? – Ben Jones Apr 12 '23 at 04:37
117

You should create a custom exception that extends Error:

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

Note: Setting this.name is required for .toString() and .stack to print InvalidArgumentException instead of Error.

For older Javascript environments, you can do prototypical inheritance:

function InvalidArgumentException(message) {
    this.message = message;
    // Use V8's native method if available, otherwise fallback
    if ("captureStackTrace" in Error)
        Error.captureStackTrace(this, InvalidArgumentException);
    else
        this.stack = (new Error()).stack;
}

InvalidArgumentException.prototype = Object.create(Error.prototype);
InvalidArgumentException.prototype.name = "InvalidArgumentException";
InvalidArgumentException.prototype.constructor = InvalidArgumentException;

This is basically a simplified version of what disfated posted with the enhancement that stack traces work on Firefox and other browsers.

Both satisfy all of these tests:

Usage:

throw new InvalidArgumentException();
var err = new InvalidArgumentException("Not yet...");

And it will behave is expected:

err instanceof InvalidArgumentException          // -> true
err instanceof Error                             // -> true
InvalidArgumentException.prototype.isPrototypeOf(err) // -> true
Error.prototype.isPrototypeOf(err)               // -> true
err.constructor.name                             // -> InvalidArgumentException
err.name                                         // -> InvalidArgumentException
err.message                                      // -> Not yet...
err.toString()                                   // -> InvalidArgumentException: Not yet...
err.stack                                        // -> works fine!
asselin
  • 1,831
  • 2
  • 13
  • 18
83

You could implement your own exceptions and their handling for example like here:

// define exceptions "classes" 
function NotNumberException() {}
function NotPositiveNumberException() {}

// try some code
try {
    // some function/code that can throw
    if (isNaN(value))
        throw new NotNumberException();
    else
    if (value < 0)
        throw new NotPositiveNumberException();
}
catch (e) {
    if (e instanceof NotNumberException) {
        alert("not a number");
    }
    else
    if (e instanceof NotPositiveNumberException) {
        alert("not a positive number");
    }
}

There is another syntax for catching a typed exception, although this won't work in every browser (for example not in IE):

// define exceptions "classes" 
function NotNumberException() {}
function NotPositiveNumberException() {}

// try some code
try {
    // some function/code that can throw
    if (isNaN(value))
        throw new NotNumberException();
    else
    if (value < 0)
        throw new NotPositiveNumberException();
}
catch (e if e instanceof NotNumberException) {
    alert("not a number");
}
catch (e if e instanceof NotPositiveNumberException) {
    alert("not a positive number");
}
Lorenzo Polidori
  • 10,332
  • 10
  • 51
  • 60
Sergey Ilinsky
  • 31,255
  • 9
  • 54
  • 56
  • 3
    The MSN website carries this warning about condition catches: **Non-standard** This feature is non-standard and is not on a standards track. Do not use it on production sites facing the Web: it will not work for every user. There may also be large incompatibilities between implementations and the behavior may change in the future. – Lawrence Dol Feb 18 '14 at 22:51
41

Yes. You can throw anything you want: integers, strings, objects, whatever. If you want to throw an object, then simply create a new object, just as you would create one under other circumstances, and then throw it. Mozilla's Javascript reference has several examples.

Rob Kennedy
  • 161,384
  • 21
  • 275
  • 467
27
function MyError(message) {
 this.message = message;
}

MyError.prototype = new Error;

This allows for usage like..

try {
  something();
 } catch(e) {
  if(e instanceof MyError)
   doSomethingElse();
  else if(e instanceof Error)
   andNowForSomethingCompletelyDifferent();
}
Morgan ARR Allen
  • 10,556
  • 3
  • 35
  • 33
  • Wouldn't this brief example work exactly the same way even if you didn't inherit Error's prototype? It's not clear to me what that gains you in this example. – EleventyOne Jul 27 '13 at 18:50
  • 1
    No, `e instanceof Error` would be false. – Morgan ARR Allen Jul 30 '13 at 17:43
  • Indeed. But since `e instanceof MyError` would be true, the `else if(e instanceof Error)` statement would never be evaluated. – EleventyOne Jul 31 '13 at 02:13
  • 1
    Right, this is just an example of how this style of try/catch would work. Where `else if(e instanceof Error)` would be the last catch. Likely followed by a simple `else` (which I did not include). Sort of like the `default:` in a switch statement but for errors. – Morgan ARR Allen Jul 31 '13 at 04:47
22

In short:

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) {
      const 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:

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
Community
  • 1
  • 1
JBE
  • 11,917
  • 7
  • 49
  • 51
13

ES6

With the new class and extend keywords it’s now much easier:

class CustomError extends Error {
  constructor(message) {
    super(message);
    //something
  }
}
Brunno
  • 977
  • 17
  • 28
11

I often use an approach with prototypal inheritance. Overriding toString() gives you the advantage that tools like Firebug will log the actual information instead of [object Object] to the console for uncaught exceptions.

Use instanceof to determine the type of exception.

main.js

// just an exemplary namespace
var ns = ns || {};

// include JavaScript of the following
// source files here (e.g. by concatenation)

var someId = 42;
throw new ns.DuplicateIdException('Another item with ID ' +
    someId + ' has been created');
// Firebug console:
// uncaught exception: [Duplicate ID] Another item with ID 42 has been created

Exception.js

ns.Exception = function() {
}

/**
 * Form a string of relevant information.
 *
 * When providing this method, tools like Firebug show the returned 
 * string instead of [object Object] for uncaught exceptions.
 *
 * @return {String} information about the exception
 */
ns.Exception.prototype.toString = function() {
    var name = this.name || 'unknown';
    var message = this.message || 'no description';
    return '[' + name + '] ' + message;
};

DuplicateIdException.js

ns.DuplicateIdException = function(message) {
    this.name = 'Duplicate ID';
    this.message = message;
};

ns.DuplicateIdException.prototype = new ns.Exception();
Matthias
  • 7,432
  • 6
  • 55
  • 88
11

Here is how you can create custom errors with completely identical to native Error's behaviour. This technique works only in Chrome and node.js for now. I also wouldn't recommend to use it if you don't understand what it does.

Error.createCustromConstructor = (function() {

    function define(obj, prop, value) {
        Object.defineProperty(obj, prop, {
            value: value,
            configurable: true,
            enumerable: false,
            writable: true
        });
    }

    return function(name, init, proto) {
        var CustomError;
        proto = proto || {};
        function build(message) {
            var self = this instanceof CustomError
                ? this
                : Object.create(CustomError.prototype);
            Error.apply(self, arguments);
            Error.captureStackTrace(self, CustomError);
            if (message != undefined) {
                define(self, 'message', String(message));
            }
            define(self, 'arguments', undefined);
            define(self, 'type', undefined);
            if (typeof init == 'function') {
                init.apply(self, arguments);
            }
            return self;
        }
        eval('CustomError = function ' + name + '() {' +
            'return build.apply(this, arguments); }');
        CustomError.prototype = Object.create(Error.prototype);
        define(CustomError.prototype, 'constructor', CustomError);
        for (var key in proto) {
            define(CustomError.prototype, key, proto[key]);
        }
        Object.defineProperty(CustomError.prototype, 'name', { value: name });
        return CustomError;
    }

})();

As a reasult we get

/**
 * name   The name of the constructor name
 * init   User-defined initialization function
 * proto  It's enumerable members will be added to 
 *        prototype of created constructor
 **/
Error.createCustromConstructor = function(name, init, proto)

Then you can use it like this:

var NotImplementedError = Error.createCustromConstructor('NotImplementedError');

And use NotImplementedError as you would Error:

throw new NotImplementedError();
var err = new NotImplementedError();
var err = NotImplementedError('Not yet...');

And it will behave is expected:

err instanceof NotImplementedError               // -> true
err instanceof Error                             // -> true
NotImplementedError.prototype.isPrototypeOf(err) // -> true
Error.prototype.isPrototypeOf(err)               // -> true
err.constructor.name                             // -> NotImplementedError
err.name                                         // -> NotImplementedError
err.message                                      // -> Not yet...
err.toString()                                   // -> NotImplementedError: Not yet...
err.stack                                        // -> works fine!

Note, that error.stack works absolutle correct and won't include NotImplementedError constructor call (thanks to v8's Error.captureStackTrace()).

Note. There is ugly eval(). The only reason it is used is to get correct err.constructor.name. If you don't need it, you can a bit simplify everything.

disfated
  • 10,633
  • 12
  • 39
  • 50
  • 2
    `Error.apply(self, arguments)` is [specified not to work](http://es5.github.io/#x15.11.1). I suggest [copying the stack trace instead](https://coderwall.com/p/m3-cqw) which is cross-browser compatible. – Kornel Jan 20 '14 at 11:28
7

Use the throw statement.

JavaScript doesn't care what the exception type is (as Java does). JavaScript just notices, there's an exception and when you catch it, you can "look" what the exception "says".

If you have different exception types you have to throw, I'd suggest to use variables which contain the string/object of the exception i.e. message. Where you need it use "throw myException" and in the catch, compare the caught exception to myException.

Community
  • 1
  • 1
Xn0vv3r
  • 17,766
  • 13
  • 58
  • 65
1
//create error object
var error = new Object();
error.reason="some reason!";

//business function
function exception(){
    try{
        throw error;
    }catch(err){
        err.reason;
    }
}

Now we set add the reason or whatever properties we want to the error object and retrieve it. By making the error more reasonable.

Massimiliano Kraus
  • 3,638
  • 5
  • 27
  • 47
Mateen
  • 1,631
  • 1
  • 23
  • 27
1

See this example in the MDN.

If you need to define multiple Errors (test the code here!):

function createErrorType(name, initFunction) {
    function E(message) {
        this.message = message;
        if (Error.captureStackTrace)
            Error.captureStackTrace(this, this.constructor);
        else
            this.stack = (new Error()).stack;
        initFunction && initFunction.apply(this, arguments);
    }
    E.prototype = Object.create(Error.prototype);
    E.prototype.name = name;
    E.prototype.constructor = E;
    return E;
}
var InvalidStateError = createErrorType(
    'InvalidStateError',
    function (invalidState, acceptedStates) {
        this.message = 'The state ' + invalidState + ' is invalid. Expected ' + acceptedStates + '.';
    });

var error = new InvalidStateError('foo', 'bar or baz');
function assert(condition) { if (!condition) throw new Error(); }
assert(error.message);
assert(error instanceof InvalidStateError);  
assert(error instanceof Error); 
assert(error.name == 'InvalidStateError');
assert(error.stack);
error.message;

Code is mostly copied from: What's a good way to extend Error in JavaScript?

Community
  • 1
  • 1
Peter Tseng
  • 13,613
  • 4
  • 67
  • 57
1

An alternative to the answer of asselin for use with ES2015 classes

class InvalidArgumentException extends Error {
    constructor(message) {
        super();
        Error.captureStackTrace(this, this.constructor);
        this.name = "InvalidArgumentException";
        this.message = message;
    }
}
Community
  • 1
  • 1