Typed EventEmitter
The Emitter<Events> class gets a description of its events: new Emitter<{ login: { userId: number }; message: string }>(). on(event, handler) subscribes a handler and returns an unsubscribe function, emit(event, payload) calls the handlers of that event.
Make the types precise: an event name can only be one of the keys of Events, and the payload type in the handler and in emit comes from that particular event.
Ctrl+Enter runs the tests. Tab indents; to leave the editor with the keyboard, press Esc and then Tab. Your code is saved in this browser.
Tests (7)
Type tests are checked by the TypeScript compiler in strict mode. Expect<Equal<A, B>> passes when the types match exactly, and // @ts-expect-error passes when the next line does not compile. The first run downloads the compiler, so it takes a few seconds.
- types
type E = { login: { userId: number }; message: string }; const e = new Emitter<E>(); e.on('login', (payload) => { type _ = Expect<Equal<typeof payload, { userId: number }>>; });
- types
type E = { login: { userId: number }; message: string }; const e = new Emitter<E>(); // @ts-expect-error - message carries a string e.emit('message', 42);
- types
type E = { login: { userId: number }; message: string }; const e = new Emitter<E>(); // @ts-expect-error - there is no such event e.on('signup', () => {});
const e = new Emitter(); const a = fn(); const b = fn(); e.on('message', a); e.on('message', b); e.emit('message', 'hi'); expect(a).toHaveBeenCalledWith('hi'); expect(b).toHaveBeenCalledWith('hi');
const e = new Emitter(); const h = fn(); const off = e.on('login', h); e.emit('login', { userId: 1 }); off(); e.emit('login', { userId: 2 }); expect(h).toHaveBeenCalledTimes(1); expect(h).toHaveBeenLastCalledWith({ userId: 1 });
const e = new Emitter(); expect(() => e.emit('nobody-listens', 1)).not.toThrow();
const e = new Emitter(); const a = fn(); const b = fn(); e.on('x', a); e.on('y', b); e.emit('x', 1); expect(a).toHaveBeenCalledTimes(1); expect(b).toHaveBeenCalledTimes(0);