Let's assume that in my codebase I have two different implementations of an EventEmitter. For example something like this:
type HandlerA = (data: boolean) => void;
type HandlerB = (data: number) => void; // HandlerB is somehow different from HandlerA
type EventEmitterA = {
on(eventName: string, handler: HandlerA): void;
...
};
type EventEmitterB = {
on(eventName: string, handler: HandlerB): void;
...
};
Now let's also assume I have a JS module that is able to work with both implementations:
Something like:
class EventDisposer {
registerEvent(sender, eventName, handler) {
sender.on(eventName, handler);
}
...
}
This module is used across the codebase (mix of TS and JS). I want to convert this module from JS to TS.
Basic attemp:
type Sender = EventEmitterA | EventEmitterB;
type Handler = HandlerA | HandlerB;
class EventDisposer {
registerEvent(sender: Sender, eventName: string, handler: Handler) {
sender.on(eventName, handler);
}
}
This doesn't work because of:
Argument of type 'Handler' is not assignable to parameter of type 'HandlerA & HandlerB'
.
What I would like to do is infer the type of Handler based on the type of the Sender (Emitter) so I don't need to change anything on the caller side. Something like:
type ExtractHandler<S extends Sender> = S extends EventEmitterA ? HandlerA : HandlerB;
type handlerA = ExtractHandler<EventEmitterA>;
type handlerB = ExtractHandler<EventEmitterB>;
class EventDisposer2 {
registerEvent<S extends Sender, H = ExtractHandler<S>>(sender: S, eventName: string, handler: H) {
sender.on(eventName, handler);
}
}
But also this doesn't work because of:
Argument of type 'H' is not assignable to parameter of type 'HandlerA & HandlerB'.
Is it possible to narrow the type of the handler based on the type of the sender? I guess I can use a type guard inside registerEvent
.
Full example: https://tsplay.dev/wQ8o7W
Thanks!