gaspard gaspard - 23 days ago 10
TypeScript Question

How to use TypeScript Map with error handlers?

Why does TypeScript complain about this code ?

class FooError extends Error {
}

const map = new Map<ErrorConstructor, any> ([
[Error, 'do this'],
[FooError, 'do that']
])



Type 'typeof FooError' is not assignable to type 'ErrorConstructor'.

Answer Source

Well, let's look at the definition of ErrorConstructor:

interface ErrorConstructor {
    new(message?: string): Error; // is a constructor
    (message?: string): Error; // is callable
    readonly prototype: Error; // has an Error prototype
}

So, an ErrorConstructor needs to be a constructor which takes an optional string argument, and it also needs to be callable as a function, and it needs to have an Error prototype. Let's look at your FooError class:

class FooError extends Error {
}
FooError.prototype; // okay, type FooError
new FooError("okay"); // okay
FooError("oops"); // error

It has a FooError prototype, which is fine since FooError is a subtype of Error. It is a constructor, and it does accept an argument since it defers to the superclass Error, which is an ErrorConstructor. But it's not callable as a function. So FooError is not an ErrorConstructor.


At this point, you need to decide if you actually care about it being an ErrorConstructor. Do you plan to call it as FooError('msg') instead of new FooError('msg')? I doubt it. For that matter, do you care that the constructor's prototype is of type FooError? Probably not. In that case, don't use ErrorConstructor. Instead, use the following interface which only cares about being an optional-one-string-arg constructor:

interface NoFrillsErrorConstructor {
  new(message?: string): Error;
}

Now your code will work:

const map = new Map<NoFrillsErrorConstructor, any> ([
  [Error, 'do this'],
  [FooError, 'do that']
])

And all will be well, as long as you only use the map's keys as constructors:

map.forEach((val, err) => {
  err.prototype; // exists, but is type any.  Who cares, right?
  new err(); // okay
  err(); // not okay
})

(If you do care about FooError conforming to ErrorConstructor, that can be arranged, but it's a bit more annoying. Let me know if you'd like me to elaborate.)

Anyway, hope that helps; good luck!