9

The following is the code that works best for displaying custom errors in Chrome Devtools, Node.js, etc. Based on this StackOverflow answer.

function CustomErr (message) {
  var err = new Error(message)
  Object.setPrototypeOf(err, CustomErr.prototype)
  return err
}

CustomErr.prototype = Object.create(Error.prototype, {
  name: { value: 'Custom Error', enumerable: false }
})

However, when I convert it to Typescript:

function CustomErr (message: string) {
  var err = new Error(message)
  Object.setPrototypeOf(err, CustomErr.prototype)
  return err
}

CustomErr.prototype = Object.create(Error.prototype, {
  name: { value: 'Custom Error', enumerable: false }
})

Calling throw new CustomErr("something went wrong") shows this error:

'new' expression, whose target lacks a construct signature, implicitly has an 'any' type.ts(7009)

What can I do to correctly type-annotate my code? If you can find another equivalent code solution, feel free to suggest it, but it MUST have the same behavior in Chrome DevTools (this alone of all solutions I tried displays a custom error name nicely). Thanks!

EDIT: Need to support older browsers, so I can't use ES6 classes. I'd prefer not to transpile classes to ES6 because I'm creating a lightweight library, and a class polyfill alone is 10% of my entire codesize.

So to recap, how can I annotate the code I have now?

Ben Gubler
  • 1,393
  • 3
  • 18
  • 32
  • Are you getting this error at compile time? – Sohan Dec 20 '19 at 05:37
  • you have to compile (or at least traspile) typescript, what do you mean by not to transpile... you wouldnt be able to run typescript without it. – Juraj Kocan Dec 20 '19 at 08:12
  • If you do want a lightweight ES5 library, you may really want to consider writing a lightweight ES5 library and do not touch TypeScript in the process. And vice versa: if you write TypeScript, let the tools worry about JavaScript. – tevemadar Dec 20 '19 at 14:44

3 Answers3

14

You can declare class, but implement it with a function. This way output (the resulting javascript) won't be affected, but typescript will treat the CustomErr as a "newable":

declare class CustomErr extends Error {
    constructor(message: string);
}

function CustomErr(message: string) {
    var err = new Error(message)
    Object.setPrototypeOf(err, CustomErr.prototype)
    return err
}

CustomErr.prototype = Object.create(Error.prototype, {
    name: { value: 'Custom Error', enumerable: false }
})

throw new CustomErr("something went wrong") // no error now

Playground

Aleksey L.
  • 35,047
  • 10
  • 74
  • 84
  • 2
    How would you export the type and function? TypeScript tells me I am not allowed to export it – David Alsh Aug 05 '20 at 13:00
  • Which error do you get? https://www.typescriptlang.org/play?#code/KYDwDg9gTgLgBAE2AYwDYEMrDm9BnPOAYQFc8YIBbAUSijlBmADsFDapo4BvAKDgE4IzclBLIKUABSVgBdAHNgALjiiAlswUBKANy8Avr16hIsOADMSzCeuHEyFGnRly8ilWphRNOnv0EAN0wGOjgAXjhmYAB3OA5oV3klbQCBAHkAIwArFBgAOjxgGAAFTgoYAE8wYHSLKWA6ABoHcioOfLByiCqa1ME4LBgSKGZQqENjUjbnKE7u3uxIrNyJfOQsdCYpBLmunp7q4Ba+AeZ0WVVuOGDUEk8AcmmneLpoB5aWElkodEzUTwWdCoIpwIwGVK8GAAC04cWicWe7RcACI8FRitDfHAYix4DFOFoUdogA – Aleksey L. Aug 05 '20 at 14:42
  • 1
    Hi @AlekseyL. I have used the approach you specified but getting an error. Please have a look https://stackoverflow.com/questions/64323473/typeerror-duplicate-declaration-module-build-failed-error-in-typescript-projec Thanks in advance! – Dolly Oct 12 '20 at 18:37
  • I agree with @DavidAlsh - `declare` was [intended](https://www.typescriptlang.org/docs/handbook/declaration-files/by-example.html) to define non-TS external references (e.g. C's `extern`), when exporting a `declare class` & `function` with the same name you receive errors. – mlhDev Jan 14 '22 at 14:18
  • There is on big problem with this approach. Function declared this way is not gonna be typechecked or be subjected to intellisense. See an example: https://www.typescriptlang.org/play?#code/CYUwxgNghgTiAEkoGdnwGIHtPwN4F8BuAKADMBXAOzABcBLTSjbAChoAs7kAuPfAGnhReyGjDqUA5gEo+xSiADuzTC2mF4Aek3wAcjhAwYmGEIBGmcjXgcEAIih34cAI7k6cYPAAOsKAFsQGkMAOmIgA – Onkeltem Apr 02 '22 at 15:10
3

I was able to implement custom errors in TypeScript by doing

interface Exception {
    code: number;
    message: string;
}

export const Exception = (function (this: Exception, code: number, message: string) {
    this.code = code;
    this.message = message;
} as unknown) as { new (code: number, message: string): Exception };

Then I can use it anywhere in my codebase like

throw new Exception(403, 'A custom error');

Or in an async operation

export const CreateUser = async ({ email, password }: { email: string; password: string }): Promise<IUser> => {
    try {
        // some async operations

        throw new Exception(403, 'Error creating user');
    } catch (error) {
        return Promise.reject(error);
    }
};

  • This worked over [the accepted answer](https://stackoverflow.com/a/59435103/295686). If you split the naming between `Exception` for the function and `IException` for the interface, the `export const Exception` is the only use of `Exception` - the other three references would be `IException` – mlhDev Jan 14 '22 at 14:24
2

I still have no idea how to annotate my code, but just changing throw new CustomErr('err') to throw CustomErr('err') fixed my problem. Though JS allows you to use the new constructor, TypeScript doesn't.

Ben Gubler
  • 1,393
  • 3
  • 18
  • 32
  • I think for this very simplistic version of this problem, this should be the accepted answer. – notAChance Mar 23 '20 at 00:06
  • 4
    Not using the `new` operator sidesteps all of the things that `new` does; I don't think that's a safe alternative here. https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Operators/new#Description – Salem Nov 06 '20 at 20:58
  • I have to agree with @Salem on this one. – samvv Apr 03 '21 at 17:58