Inherit from Error breaks `instanceof` check in TypeScript
Asked Answered
D

2

10

Can someone explain me why the error instanceof CustomError part of below code is false ?

class CustomError extends Error {}

const error = new CustomError();

console.log(error instanceof Error); // true
console.log(error instanceof CustomError); // false ???

class ParentClass {}
class ChildClass extends ParentClass { }

const child = new ChildClass();

console.log(child instanceof ParentClass); // true
console.log(child instanceof ChildClass); // true

Is something special about Error object? I would like to make my own error types that i can check against.

Btw i've checked the above code on latest TypeScript Playground

Diffidence answered 11/5, 2017 at 9:55 Comment(0)
D
7

Turns out an change has been introduced in [email protected] that breaks this pattern. The whole breaking change is described here.

In general it seems it's too complicated/error to even go with this direction.

Probably better to have own error object and keeps some original Error as an property:

class CustomError {
    originalError: Error;

    constructor(originalError?: Error) {
        if (originalError) {
            this.originalError = originalError
        }
    }
}

class SpecificError extends CustomError {}

const error = new SpecificError(new Error('test'));

console.log(error instanceof CustomError); // true
console.log(error instanceof SpecificError); // true
Diffidence answered 11/5, 2017 at 10:47 Comment(0)
C
0

If you have TypeScript set to compile to ES5 or some other pre-ES2015 version of JavaScript, TypeScript can't correctly subclass Error because it was impossible to do so prior to ES2015. So instead it produces something that's an Error but not an CustomError (in your case). If you have TypeScript target ES2015 or later, it works correctly.

The generated JavaScript for your CustomError code looks like this if it's set to ES5 output:

"use strict";
var __extends = (this && this.__extends) || (function () {
    var extendStatics = function (d, b) {
        extendStatics = Object.setPrototypeOf ||
            ({ __proto__: [] } instanceof Array && function (d, b) { d.__proto__ = b; }) ||
            function (d, b) { for (var p in b) if (Object.prototype.hasOwnProperty.call(b, p)) d[p] = b[p]; };
        return extendStatics(d, b);
    };
    return function (d, b) {
        if (typeof b !== "function" && b !== null)
            throw new TypeError("Class extends value " + String(b) + " is not a constructor or null");
        extendStatics(d, b);
        function __() { this.constructor = d; }
        d.prototype = b === null ? Object.create(b) : (__.prototype = b.prototype, new __());
    };
})();
var CustomError = /** @class */ (function (_super) {
    __extends(CustomError, _super);
    function CustomError() {
        return _super !== null && _super.apply(this, arguments) || this;
    }
    return CustomError;
}(Error));
var error = new CustomError();
console.log(error instanceof Error); // true
console.log(error instanceof CustomError); // true

Playground Link

But in ES2015 and above, it's possible to correctly inherit from Error (and Array, which also had this problem). Here's your code targeting ES2015:

"use strict";
class CustomError extends Error {
}
const error = new CustomError();
console.log(error instanceof Error); // true
console.log(error instanceof CustomError); // true

...which shows true, true.

Playground Link

The good news is that here in 2022, unless you need to target truly obsolete environments (IE11, specifically), you can update your TypeScript configuration to target at least ES2015 (and almost certainly something even later than that).

Craftwork answered 30/8, 2022 at 7:54 Comment(0)

© 2022 - 2024 — McMap. All rights reserved.