Open djrenren opened 1 year ago
We can remove the degree of freedom from the type by simply not allowing the EVENT
variable to define return types. Instead, using a map from event name to a list of parameter types.
- * @template {{[key in keyof EVENTS]: function(...any):void}} EVENTS
+ * @template {{[key in keyof EVENTS]: any[]}} EVENTS
*/
export class ObservableV2 {
and then on the methods:
- * @param {EVENTS[NAME]} f
+ * @param {(...args: EVENTS[NAME]) => void} f
*/
on (name, f) {
So now usage would look like:
let x = new ObservableV2<{"someEvent": [Foo, number, string]}>();
x.on('someEvent', (foo: Foo, num: number, str: string) => { /* ... */ });
And just to close the loop, this change does allow me to write the waitUntil
abstraction like so:
type EventTable = {[event: string]: any[]};
export const waitUntil = <
V extends EventTable,
E extends string & keyof V
>(
o: ObservableV2<V>,
e: E,
t: (...args: V[E]) => boolean
): Promise<V[E]> => new Promise(resolve => {
let cb: (...args: V[E]) => void;
cb = (...args) => {
if (t(...args)) {
o.off(e, cb);
resolve(args)
}
}
o.on(e, cb);
});
As shown above, the return type information in the EVENTS
map should be ignored. The solution above removes them so there's no more extraneous information in the type, but we could just ignore the return type by changing the types on the methods:
- * @param {EVENTS[NAME]} f
+ * @param {(...args: Parameters<EVENTS[NAME]>) => void} f
*/
on (name, f) {
Describe the bug
The
ObservableV2
class allows the specification of a type which defines the set of events and callbacks, but the template is overly permissive. Namely, theEVENT
parameter can constrain the callbacks such that every callback must return a value. This possibility makes it very difficult to write well-typed abstractions overObservableV2
(they must handle the case whereEVENTS
has been instantiated this way).To Reproduce
Here's a small example of this in typescript:
Which results in the error:
Expected behavior
ObservableV2
should always allow callbacks that returnvoid
.Additional context I ran into this problem while trying to write a utility function called
waitUntil
that runs a test on all emitted events and resolves a promise when it successfully passes the test. The untyped form is basically:This lets me write nice little functions like:
Unfortunately it's impossible (or at least beyond me) to make the function
cb
insidewaitUntil
typecheck because the observable could require that callbacks for this event return a value.