Why doesn't instanceof work on instances of Error subclasses under babel-node?

Javascriptnode.jsEcmascript 6Babeljs

Javascript Problem Overview


I am seeing that the instanceof operator doesn't work on instances of Error subclasses, when running under babel-node version 6.1.18/Node version 5.1.0 on OS X. Why is this? The same code works well in the browser, try my fiddle for an example.

The following code outputs true in the browser, whereas under babel-node it's false:

class Sub extends Error {
}

let s = new Sub()
console.log(`The variable 's' is an instance of Sub: ${s instanceof Sub}`)

I can only imagine this being due to a bug in babel-node, since instanceof works for other base classes than Error.

.babelrc

{
  "presets": ["es2015"]
}

Compiled Output

This is the JavaScript compiled by babel 6.1.18:

'use strict';

function _classCallCheck(instance, Constructor) { if (!(instance instanceof Constructor)) { throw new TypeError("Cannot call a class as a function"); } }

function _possibleConstructorReturn(self, call) { if (!self) { throw new ReferenceError("this hasn't been initialised - super() hasn't been called"); } return call && (typeof call === "object" || typeof call === "function") ? call : self; }

function _inherits(subClass, superClass) { if (typeof superClass !== "function" && superClass !== null) { throw new TypeError("Super expression must either be null or a function, not " + typeof superClass); } subClass.prototype = Object.create(superClass && superClass.prototype, { constructor: { value: subClass, enumerable: false, writable: true, configurable: true } }); if (superClass) Object.setPrototypeOf ? Object.setPrototypeOf(subClass, superClass) : subClass.__proto__ = superClass; }

var Sub = (function (_Error) {
  _inherits(Sub, _Error);

  function Sub() {
    _classCallCheck(this, Sub);

    return _possibleConstructorReturn(this, Object.getPrototypeOf(Sub).apply(this, arguments));
  }

  return Sub;
})(Error);

var s = new Sub();
console.log('The variable \'s\' is an instance of Sub: ' + (s instanceof Sub));

Javascript Solutions


Solution 1 - Javascript

tl;dr If you're on Babel 6, you can use https://www.npmjs.com/package/babel-plugin-transform-builtin-extend

Extending builtin types like Array and Error and such has never been supported in Babel. It is perfectly valid in a real ES6 environment, but there are requirements to make it work that are very difficult to transpile in a way that is compatible with older browsers. It "worked" in Babel 5 in that it didn't throw an error, but objects instantiated from the extended subclass did not work like they were supposed to, for example:

class MyError extends Error {}

var e1 = new MyError();
var e2 = new Error();

console.log('e1', 'stack' in e1);
console.log('e2', 'stack' in e2);

results in

e1 false
e2 true

While it did not error out, the subclass does not properly get a 'stack' like errors are supposed to. Similarly, if you were to extend Array it might behave somewhat like an array, and have array methods, but it did not behave fully like an array.

The Babel 5 documentation specifically called this out as an edge-case of classes to be aware of.

In Babel 6, classes were changed to be more spec-compliant in how subclassing is handled, and a side-effect of that is that now the above code will still not work, but it will not work in a different way than before. This has been covered in https://phabricator.babeljs.io/T3083, but I'll elaborate here on a potential solution.

To return Babel 5 subclassing behavior (which remember, is still not right or recommended), you can wrap the builtin constructor in your own temporary class, e.g.

function ExtendableBuiltin(cls){
    function ExtendableBuiltin(){
        cls.apply(this, arguments);
    }
    ExtendableBuiltin.prototype = Object.create(cls.prototype);
    Object.setPrototypeOf(ExtendableBuiltin, cls);

    return ExtendableBuiltin;
}

With this helper, rather than doing

class MyError extends Error {}

do

class MyError extends ExtendableBuiltin(Error) {}

In your specific case however, you have said that you are on Node 5.x. Node 5 has support for native ES6 classes without transpiling. I'd recommend you use those by dropping the es2015 preset and instead using node5 so you get native classes, among other things. In that context,

class MyError extends Error {}

will work the way you expect.

For people not on Node 4/5, or recent Chrome only, you may want to consider using something like https://www.npmjs.com/package/error. You can also explore https://www.npmjs.com/package/babel-plugin-transform-builtin-extend. The approximate option from that is the same behavior from Babel 5. Beware that the non-approximate behavior is definitely edge-casey and may not work in 100% of cases.

Solution 2 - Javascript

instanceof will not work for subclassed errors if the compile target is set to "es5". I set the target to "es6" in my tsconfig.json and instanceof produced the right result.

Attributions

All content for this solution is sourced from the original question on Stackoverflow.

The content on this page is licensed under the Attribution-ShareAlike 4.0 International (CC BY-SA 4.0) license.

Content TypeOriginal AuthorOriginal Content on Stackoverflow
Questionaknuds1View Question on Stackoverflow
Solution 1 - JavascriptloganfsmythView Answer on Stackoverflow
Solution 2 - JavascriptbelvederefView Answer on Stackoverflow