Skip to content
GreenSnippets

Type-safe event emitter

An event emitter whose event names and payloads are checked by the compiler through a generic map type.

TypeScript
type Handler<T> = (payload: T) => void;

class Emitter<Events extends Record<string, unknown>> {
  private handlers: { [K in keyof Events]?: Set<Handler<Events[K]>> } = {};

  on<K extends keyof Events>(event: K, handler: Handler<Events[K]>): () => void {
    (this.handlers[event] ??= new Set()).add(handler);
    return () => this.handlers[event]?.delete(handler);
  }

  emit<K extends keyof Events>(event: K, payload: Events[K]): void {
    for (const handler of [...(this.handlers[event] ?? [])]) handler(payload);
  }
}

Example / expected output

Output
const bus = new Emitter<{ login: { userId: string }; logout: undefined }>();
bus.on("login", ({ userId }) => console.log(userId));
bus.emit("login", { userId: "42" });

How it works

Describe your events once as a map from name to payload type. on and emit are generic over the key, so a wrong event name or payload is a compile error and handlers get the exact payload type.