Why is it not possible to have multiple signatures for a "handler" function variable?
Take this ideal, but invalid code snippet as an example:
class MyEntityService {
private handleThing: (a: undefined) => undefined;
private handleThing: <T extends object>(a: T) => T;
private handleThing: <T extends object>(a: T | undefined) => object | T {
if (!a) return undefined;
// Various calls to other private methods here
return a;
}
}
What I actually want is something along those lines: handleThing
is an event handler or a promise's then
body that needs to have lexical this
binding (an arrow function is the easiest way to achieve that). The intention is that handleThing
maintains multiple signatures, so that the most suitable one may be picked up by context (i.e. depending on where it is used).
I also tried the folllowing, but the handler variable ended up having type any
, i.e. all the typing was effectively dropped off:
class MyEntityService {
private handleThing = this.handleThing_.bind(this);
private handleThing_(a: undefined): undefined;
private handleThing_<T extends object>(a: T): T;
private handleThing_<T extends object>(a: T | undefined): T | undefined {
if (!a) return undefined;
// Various calls to other private methods here
return a;
}
}
Having only handleThing: <T extends object>(a: T | undefined) => object | undefined
is not ideal:
An option would be to use async
functions and do away with handler function variables, but that would go against the established code conventions my team set up for the project I had this dilemma in.
Therefore, I ask:
To make your first snippet work, you need to treat handleThing
as an initialized property and not a method; that means you give it a single type annotation and a single value. Note that the type of an overloaded function can be represented either as the intersection of each signature (e.g., ((x: string)=>number) & ((x: number)=>string)
), or as a single object type with multiple bare function signatures (e.g., { (x: string): number; (x: number): string; }
). Like this:
class MyEntityService {
private handleThing: {
(a: undefined): undefined;
<T extends object>(a: T): T;
} = <T extends object>(a: T | undefined) => {
if (!a) return undefined;
// Various calls to other private methods here
return a;
}
}
The second snippet will work as you expect once you update to TypeScript 3.4 or above, since TypeScript 3.4 added better support for inferring generic types from uses of generic functions. In TypeScript 3.3 or below, the return value of bind()
will have all the generics stripped out and replaced with any
as you saw.
Finally, I'm not sure why you don't go for a single signature:
class MyEntityService {
private handleThing = <T extends object | undefined>(a: T): T => {
if (!a) return undefined as T; // have to assert here
// Various calls to other private methods here
return a;
}
}
since (x: undefined) => undefined
should match that signature if you let T
range over both object
and undefined
.
Anyway, hope that helps; good luck!