A typed event emitter in TypeScript
A map of event names to payloads makes on and emit type-safe.
type EventMap = {
run: { lang: string; tpm: number };
cleared: { set: string };
error: Error;
};
class Emitter<Events extends Record<string, unknown>> {
#handlers = new Map<keyof Events, Set<(payload: never) => void>>();
on<K extends keyof Events>(type: K, handler: (payload: Events[K]) => void) {
const set = this.#handlers.get(type) ?? new Set();
set.add(handler as (payload: never) => void);
this.#handlers.set(type, set);
return () => set.delete(handler as (payload: never) => void);
}
emit<K extends keyof Events>(type: K, payload: Events[K]): number {
const set = this.#handlers.get(type);
set?.forEach((handler) => (handler as (p: Events[K]) => void)(payload));
return set?.size ?? 0;
}
}
const bus = new Emitter<EventMap>();
bus.on("run", ({ tpm }) => console.log("tpm", tpm));
console.log(bus.emit("run", { lang: "ts", tpm: 98 }));
// bus.emit("run", { lang: "ts" }); // error: tpm is missing
How it works
- The event map is the single source of truth.
K extends keyof Eventsties the payload to the name.- A wrong payload, or an unknown event, fails to compile.
Keywords and builtins used here
ErrorEventsMapSetasclassconstextendsnevernumberreturnstringthistype
The run, in numbers
- Lines
- 27
- Characters to type
- 905
- Tokens
- 280
- Three-star pace
- 110 tpm
At the three-star pace of 110 tokens a minute, this run takes about 153 seconds.
Step 1 of 2 in Typed events, step 3 of 8 in Async & modules.