Reputation: 3659
I was trying to implement a custom Error class, which would accept different kinds of data depending on the error code. I was pretty sure that was I wanted to do was too complex for typescript to infer, but, to my surprise, this worked:
const enum ERROR_CODES {
E_AUTHORIZATION = 'Authorization error',
E_REQUEST = 'Request failed',
E_INVALID = 'Invalid data',
}
interface ERROR_TYPES {
[ERROR_CODES.E_AUTHORIZATION]: string
[ERROR_CODES.E_REQUEST]: { url: string; status: number }
[ERROR_CODES.E_INVALID]: { action: string; field: string }
}
class MyError<TError extends ERROR_CODES> extends Error {
constructor(
public readonly code: TError,
public readonly data: ERROR_TYPES[TError],
) { super(code) }
}
Now I can use it like this:
throw new MyError(ERROR_CODES.E_AUTHORIZATION, 'whatever')
throw new MyError(ERROR_CODES.E_AUTHORIZATION, {
operation: 'login',
field: 'email',
})
This works fine. Another thing I wanted to do is to create such error codes that need no data. Since it is possible to define functions like this:
function foo(bar: void) {}
foo()
My next logical step was to write this:
const enum ERROR_CODES {
// ...
E_UNKNOWN = 'Unknown error'
}
interface ERROR_TYPES {
// ...
[ERROR_CODES.E_UNKNOWN]: void
}
Now typescript behaves in quite a strange way. If I write this:
throw new MyError(ERROR_CODES.E_UNKNOWN, undefined)
it works. If I write this:
throw new MyError(ERROR_CODES.E_UNKNOWN)
it says Expected 2 arguments, but got 1.
. If I write something like this:
throw new MyError(ERROR_CODES.E_UNKNOWN, void 0)
which should basically be the same as the first example, then it says Expected 'undefined' and instead saw 'void'.
. What is happening here and is it possible to make the second example work with just one argument?
Upvotes: 0
Views: 288
Reputation: 330466
I can't reproduce "expected undefined
but instead saw void
".
Here's how I might approach what you're trying to do. To "optionally make a function parameter optional", I'd represent the parameter list as as rest tuple. Consider this type alias:
type UndefParamToOptional<T> = undefined extends T ? [T?] : [T];
The type UndefParamToOptional<string>
is just [string]
, but UndeParamToOptional<string | undefined>
is [string?]
. That's a tuple with a single optional element corresponding to an optional function parameter. Then we can implement MyError
like this:
class MyError<TError extends ERROR_CODES> extends Error {
constructor(
code: TError,
...[data]: UndefParamToOptional<ERROR_TYPES[TError]>
);
constructor(public readonly code: TError, public readonly data: ERROR_TYPES[TError]) {
super(code);
this.data = data;
}
}
I'm using a single overload signature to show how you intend to call it, while leaving the implementation signature unchanged.
Now this should behave as you expect:
throw new MyError(ERROR_CODES.E_UNKNOWN); // okay
Hope that helps. Good luck!
Upvotes: 1