Skip to content

Instantly share code, notes, and snippets.

@mbrowne
Last active January 24, 2018 07:36
Show Gist options
  • Star 0 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save mbrowne/4af54767dcb3d529648f5a8aa11d6348 to your computer and use it in GitHub Desktop.
Save mbrowne/4af54767dcb3d529648f5a8aa11d6348 to your computer and use it in GitHub Desktop.
Object.setPrototypeOf
var log = console.log.bind(console);
function CustomError(message) {
//...
}
//By default, the prototype of all new functions is Function.prototype
log(Object.getPrototypeOf(CustomError) === Function.prototype); //true
//Setting the prototype of CustomError constructor using Object.setPrototypeOf()
//changes the prototype of the constructor function itself, not new instances created by it.
Object.setPrototypeOf(CustomError, Error.prototype);
var e = new CustomError();
log( Object.getPrototypeOf(e) === Error.prototype ) //false
log( Object.getPrototypeOf(e) === CustomError.prototype ) //true
log( e instanceof CustomError ) //true
//this should be true; it's false because it's not actually inheriting from Error
log( e instanceof Error ) //false
//The only thing that changed was the prototype of the constructor itself
log( Object.getPrototypeOf(CustomError) )
//The prototype property on constructor functions is not the same as the actual prototype of the object.
//(That's why the Object.setPrototypeOf() approach doesn't work correctly.)
log( Object.getPrototypeOf(CustomError) === CustomError.prototype ) //false
log( Object.getPrototypeOf(CustomError) === CustomError.__proto__ ) //true
log( Object.getPrototypeOf(CustomError) === Object.getPrototypeOf(e) ) //false
//THE CORRECT WAY
function CustomError2(message) {
//...
}
//In order to affect the prototype of *instances* of CustomError, you can simply
//set the prototype property of the constructor
CustomError2.prototype = Object.create(Error.prototype);
var e2 = new CustomError2();
log( Object.getPrototypeOf(e2) === CustomError2.prototype ) //true
//Now instanceof works correctly
log( e2 instanceof CustomError2 ) //true
log( e2 instanceof Error ) //true
//You *could* use Object.setPrototypeOf() to *change* the prototype of an already-existing
//error object. That's really the only reason you should ever need to use Object.setPrototypeOf()
// - if you already have an object and want to change its prototype. But this is less efficient
//and usually unnecessarily complicated compared with setting the prototype to what you want it
//to be in the first place.
function CustomError3(message) {}
var e3 = new CustomError3();
Object.setPrototypeOf(e3, Error.prototype);
log( Object.getPrototypeOf(e3) === CustomError3.prototype ) //false
log( Object.getPrototypeOf(e3) === Error.prototype ) //true
//There *is* one thing you can do using this technique that you can't do without Object.setPrototypeOf():
//you can make a custom error look more like a regular Error when it's shown in the console and still
//have it be an instance of your custom error type.
function CustomError4(message) {
var err = new Error(message);
Object.setPrototypeOf(err, CustomError4.prototype);
//...
return err;
}
CustomError4.prototype = Object.create(Error.prototype);
var e4 = new CustomError4();
log( e4 instanceof CustomError4 ) //true
log( e4 instanceof Error ) //true
@onury
Copy link

onury commented Dec 27, 2016

Thanks for taking the time.

Actually my first version on StackOverflow was .setPrototypeOf(CustomError.prototype, Error.prototype). (Note CustomError.prototype instead of CustomError). I changed it on an advice from a friend which I see, was a mistake.

But still, I'd insist on using the .setPrototypeOf method since it provides all the things we look for (passes instanceof test, inherits from Error, etc). As you said, it looks more like a genuine error in the console.

I think the only concern is the performance (which I think is good enough in V8).
jsperf is down and I don't have time for setting up a benchmark. I'll make one later.

@mbrowne
Copy link
Author

mbrowne commented Dec 28, 2016

Thanks for your interest in continuing the discussion, and for updating your answer, which I now think is perfectly legitimate :) But I would still say that there's little difference between Object.setPrototypeOf(CustomError.prototype, Error.prototype) and CustomError.prototype = Object.create(Error.prototype)... In fact I think the only difference is that the constructor property isn't set correctly, but I don't think that matters much in this case, and it could be easily remedied by adding CustomError.prototype.constructor = CustomError. I agree with you that the effect on performance is probably irrelevant - throwing errors is hopefully something done infrequently enough for it not to matter.

As to the way errors appear in the console, unfortunately using setPrototypeOf on CustomError.prototype isn't sufficient to make it look the same as a regular Error (at least not in Chrome or Firefox where I tested it). To achieve that you have to call new Error() directly when constructing new errors and then use setPrototypeOf on it as shown here: https://gist.github.com/mbrowne/fe45db61cea7858d11be933a998926a8.

I added a new answer on SO summarizing my general thoughts on how to subclass Error after doing some research and experimenting:
http://stackoverflow.com/a/41338601/560114

Maybe there's some benefit of using .setPrototypeOf(CustomError.prototype, Error.prototype) that I'm missing...

@onury
Copy link

onury commented Jan 5, 2017

An internal Error instance is another way but while it has the Error.prototype, it suffers the lack of a proper constructor (which will have name "Error") and the instance will not be an instance of the custom error object. However, I like the idea of setting the prototype of the internal Error instance to the CustomError prototype -- still it needs the proper constructor.

.setPrototypeOf() sets both the prototype and the constructor. So, for CustomError.prototype = Object.create(Error.prototype) you also need to set CustomError.prototype.constructor = CustomError;. This way, there will be quite no difference between implementations.

Actually I wrote a tester module for this. You can try it out in Node and various browsers. This also includes other implementations widely used. You can compare them by viewing the result output in console and generated HTML.

This test confirms that (with the constructor assignment), both .setPrototypeOf and Object.create implementations produce quite the same result. So it's really a preference to use one of them.

So combining them all; (with a little over-kill) this should be the best way to implement a custom error.

I'll update my SO answer to reflect these, soon. Thanks for this nice conversation. Pls go on if you have anything more to add/discuss.

--
Note: BTW, I don't care for supporting old IE versions so I'd still prefer .setPrototypeOf in other contexts too. 😎

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment