36

I noticed a strange behavior while defining custom error objects in Javascript:

function MyError(msg) {
    Error.call(this, msg);
    this.name = "MyError";
}
MyError.prototype.__proto__ = Error.prototype;

var error = new Error("message");
error.message; // "message"

var myError = new MyError("message");
myError instanceof Error; // true
myError.message; // "" !

Why does new Error("message") set the message property, while Error.call(this, msg); does not? Sure, I can just define this.message = msg in the MyError constructor, but I don't quite understand why it is not already set in the first place.

Philippe Plantier
  • 7,964
  • 3
  • 27
  • 40

7 Answers7

41

A. Like, Raynos said, The reason message isn't being set is that Error is a function that returns a new Error object and does not manipulate this in any way.

B. The way to do this right is to set the result of the apply from the constructor on this, 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.message = tmp.message
    // instead of this.stack = ..., a getter for more optimizy goodness
    Object.defineProperty(this, 'stack', {
        get: function () {
            return tmp.stack
        }
    })

    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 iteratted 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() ??).

Update

I created an inheritance library that does this ^ https://github.com/fresheneesz/proto

Community
  • 1
  • 1
B T
  • 57,525
  • 34
  • 189
  • 207
  • 2
    That is (sadly) the correct way of doing it. The accepted answer does not set it to type of MyError but just creates a type Error. So custom error handling is not possible. – Eleasar Sep 19 '13 at 11:23
  • 3
    You could also use `Object.create` instead `function x(){}`, then `x.prototype = Error.prototype`, then `new x()` – laconbass Dec 05 '13 at 04:05
  • PD: +1, this got me on the way – laconbass Dec 05 '13 at 04:24
  • 2
    I'm confused. If `Error` doesn't manipulate `this`, why are you doing `Error.apply(this, arguments)` instead of `Error.apply(null, arguments)`? – Matt Fenwick Mar 05 '14 at 01:23
  • Its just the standard way to cleanly pass the entirety of the function-call context to another function (ie the `this` and the arguments). I could have done Error(arguments[0]), but A. I wasn't entirely sure there couldn't be other arguments, and B. I wasn't entirely sure that *nothing* is done with `this` - there may be things it does with it that don't involve modifying it. Probably not, but I wasn't 100% sure. – B T Mar 11 '14 at 08:16
  • I'm curious how much of a performance hit is made from accessing the tmp.stack getter. I know it's expensive enough for at least V8 to make a getter... – Mike Marcacci Jul 24 '14 at 06:06
  • 1
    Interesting point mike, I hadn't actually thought about it being a getter, but I've seen the (...) in the debugger so many times, I should have realized it. Creating a getter isn't expensive, but building the stack trace might be (otherwise why would they have used a getter, right?). The most expensive stuff (recording the information that would allow the creation of a stack trace) has already been done at this point tho, so the savings aren't huge. An easy solution would be to make this.stack into a getter as well – B T Jul 24 '14 at 18:01
  • 4
    why do you need the `IntermediateInheritor`? – Onur Yıldırım Dec 07 '14 at 14:47
  • 2
    And also I don't think returning `this` is necessary. – Onur Yıldırım Dec 07 '14 at 14:58
  • 2
    You need some way to add methods to the new Error type without modifying the original Error type. The intermediate inheritor is the prototype you would attach new methods to. You might be able to just instantiate an Error object and add methods to that object, then use it as the prototype of MyError. I don't remember the intricacies of this anymore tho, cause these days I just use proto to do javascript inheritnace. – B T Dec 08 '14 at 03:14
  • I simplified and improved this approach a bit: http://jsbin.com/rolojuhuya/1/edit?js,console – Matt Kantor Feb 02 '15 at 18:39
  • regardless to the discussion, you might want to look at https://github.com/osher/error-stringify – Radagast the Brown Mar 28 '16 at 20:27
  • Apparently the custom constructor isn't displayed in the console when `new` is omitted. –  Jun 25 '17 at 19:18
  • Are there any downsides to declaring the name and the stack property on the prototype? It seems like it would save memory when newing up lots of errors. Example here: https://jsbin.com/caboqij/edit?js,console – Michael Kropat Nov 02 '18 at 13:12
16
function MyError(msg) {
    var err = Error.call(this, msg);
    err.name = "MyError";
    return err;
}

Error doesn't manipulate this, it creates a new error object which is returned. That's why Error("foo") works aswell without the new keyword.

Note this is implementation specific, v8 (chrome & node.js) behave like this.

Also MyError.prototype.__proto__ = Error.prototype; is a bad practice. Use

MyError.prototype = Object.create(Error.prototype, { 
  constructor: { value: MyError } 
});
Raynos
  • 166,823
  • 56
  • 351
  • 396
  • Thanks for the answer, and for the Object.create inheritance tip! – Philippe Plantier Jan 10 '12 at 17:24
  • 2
    This is not entirely true. Not using the `new` keyword only results in the `Error` constructor to recursively call itself with the `new` keyword, which in turn makes the stack-trace start inside the `Error` constructor instead of in place in your own code where you initialized the call. Hence, always use the `new` keyword. – Thomas Watson Apr 22 '13 at 20:00
  • I'm confused. If "`Error` doesn't manipulate `this`", why are you doing `Error.call(this, msg)` instead of, say, `Error.call(null, msg)`? – Matt Fenwick Mar 05 '14 at 01:21
  • 3
    -1 for declaring something to be a bad practice without explanation (it may well be an utterly stupid thing to do for all I know, but I have no idea why from reading your answer) and also for recommending `Object.create` as the alternative without acknowledging that it's unsupported in IE 8 and below. – Mark Amery Apr 23 '14 at 14:56
  • 2
    -1 for not subclassing correctly and having `instanceof` not work. See answer bellow for a more complete solution – thanpolas Jun 27 '14 at 07:28
  • 3
    "answer below" is rather relative. What was below on June 27th in 2014? :D – oldwizard Jun 11 '18 at 12:56
11

In Node.js you can create a custom error like this:

var util = require('util');

function MyError(message) {
  this.message = message;
  Error.captureStackTrace(this, MyError);
}

util.inherits(MyError, Error);

MyError.prototype.name = 'MyError';

See captureStackTrace in node docs

Peter Dotchev
  • 2,980
  • 1
  • 25
  • 19
2

What's wrong with doing it this way in ES6?

class MyError extends Error {
    constructor(message) {
        super(message);
        // Maintains proper stack trace (only on V8)
        if (Error.captureStackTrace) {
            Error.captureStackTrace(this, MyError);
        }
        this.appcode= 123; // can add custom props
    }
}
Fay
  • 173
  • 13
  • 1
    That was not possible back in 2012, when this question was asked. This is a similar question about ES6: https://stackoverflow.com/questions/31089801/extending-error-in-javascript-with-es6-syntax-babel – Philippe Plantier Sep 24 '18 at 08:24
1

You can use Error.captureStackTrace for filtering out unneeded line in stack trace.

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

    this.message = tmp.message;
    /*this.stack = */Object.defineProperty(this, 'stack', { // getter for more optimizy goodness
        get: function() {
            return tmp.stack;
        }
    });

    Error.captureStackTrace(this, MyError); // showing stack trace up to instantiation of Error excluding it.

    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 ...>
Karen Grigoryan
  • 5,234
  • 2
  • 21
  • 35
0

Another approach to this is to make the new error instance the prototype of this, and that way you don't have to know what properties to copy, which gets around the problems B T talked about at the end of their answer.

function MyError() {
    if (this === undefined) {
        throw TypeError("MyError must be called as a constructor");
    }
    let newErr = Error.apply(undefined, arguments);
    Object.setPrototypeOf(newErr, MyError.prototype);
    Object.setPrototypeOf(this, newErr);
}
MyError.prototype = Object.create(Error.prototype);

let me = new MyError("A terrible thing happened");
console.log(me instanceof MyError);  // true
console.log(me instanceof Error);  // true
console.log(me.message);  // A terrible thing happened

And for my money it's a bit neater. But note that Object.setPrototypeOf() (or object.__proto__ = on non ES6 compliant implementations that support it) can be very slow, so if you are using these errors on your golden paths then you may not want to do this.

daphtdazz
  • 7,754
  • 34
  • 54
0

I like a lot to make reusable .js files that I put in almost any project I participate. When i have time it will become a module.

For my errors i create a exceptions.js file and add it on my files.

Here is the example of the code inside this file:

const util = require('util');

/**
 * This exception should be used when some phat of code is not implemented.
 * @param {String} message Error message that will be used inside error.
 * @inheritDoc Error
 */
function NotImplementedException(message) {
  this.message = message;
  Error.captureStackTrace(this, NotImplementedException);
}

util.inherits(NotImplementedException, Error);

NotImplementedException.prototype.name = 'NotImplementedException';

module.exports = {
  NotImplementedException,
};

In the other files of my project i must have this require line on top of the file.

const Exceptions = require('./exceptions.js');

And to use this error you just need this.

const err = Exceptions.NotImplementedException(`Request token ${requestToken}: The "${operation}" from "${partner}" does not exist.`);

Example of a full method implementation

const notImplemented = (requestToken, operation, partner) => {
  logger.warn(`Request token ${requestToken}: To "${operation}" received from "${partner}"`);
  return new Promise((resolve, reject) => {
    const err = Exceptions.NotImplementedException(`Request token ${requestToken}: The "${operation}" from "${partner}" does not exist.`);
    logger.error(err.message);
    return reject(err);
  });
};
João Miguel
  • 163
  • 1
  • 4