andywer / typed-emitter

🔩 Type-safe event emitter interface for TypeScript
MIT License
268 stars 24 forks source link

Promise-based events.once in typed-emitter #24

Open leumasme opened 2 years ago

leumasme commented 2 years ago

Does typed-emitter have support for the "once" function of the "events" inbuilt package? https://github.com/nodejs/node/pull/26078 Used like this:

import { once } from "events"
// ...
await once(emitter, "event");

This is not typed, and its not even a function on the EventEmitter - is there a clean way to type this without reimplementing the promise-based once function? Thanks for your work on this package!

andywer commented 2 years ago

Hey @leumasme.

We might be able to "fix it" with a global type declaration extending the existing events interface, potentially under a new entry point in typed-emitter.

Haven't tried yet, though. Would be happy to give it a try if you were to prepare a PR.

PS: I do think it is a function on the EventEmitter, a static one, though.

martinheidegger commented 2 years ago

This should be possible like this:

playground

import { once, EventEmitter } from "events";
import TypedEmitter from "typed-emitter";

// --- Utils
type EventsFor <T extends TypedEmitter<any>> = T extends TypedEmitter<infer EventMap> ? keyof EventMap : never
type FirstArg <FN extends (...opts: any[]) => any> = Parameters<FN>[0]
type FirstEventArg <Emitter extends TypedEmitter<any>, Event extends EventsFor<Emitter>> = Emitter extends TypedEmitter<infer EventMap> ? FirstArg<EventMap[Event]> : never;

// --- Exported by typed-emitter
type Once = <Emitter extends TypedEmitter<any>, Event extends EventsFor<Emitter>> (emitter: Emitter, event: Event) => Promise<FirstEventArg<Emitter, Event>>

// --- Example declaration 
const emitter = (new EventEmitter()) as TypedEmitter<{
    A: (foo: number) => void
    B: (foo: string) => void
}>

// --- Example usage
const res = await (once as Once)(emitter, 'A');