Typesafe Event Emitter

Conventionally in Node.js and traditional JavaScript you have a single event emitter. This event emitter internally tracks listener for different event types e.g.

  1. const emitter = new EventEmitter();
  2. // Emit:
  3. emitter.emit('foo', foo);
  4. emitter.emit('bar', bar);
  5. // Listen:
  6. emitter.on('foo', (foo)=>console.log(foo));
  7. emitter.on('bar', (bar)=>console.log(bar));

Essentially EventEmitter internally stores data in the form of mapped arrays:

  1. {foo: [fooListeners], bar: [barListeners]}

Instead, for the sake of event type safety, you can create an emitter per event type:

  1. const onFoo = new TypedEvent<Foo>();
  2. const onBar = new TypedEvent<Bar>();
  3. // Emit:
  4. onFoo.emit(foo);
  5. onBar.emit(bar);
  6. // Listen:
  7. onFoo.on((foo)=>console.log(foo));
  8. onBar.on((bar)=>console.log(bar));

This has the following advantages:

  • The types of events are easily discoverable as variables.
  • The event emitter variables are easily refactored independently.
  • Type safety for event data structures.

Reference TypedEvent

  1. export interface Listener<T> {
  2. (event: T): any;
  3. }
  4. export interface Disposable {
  5. dispose();
  6. }
  7. /** passes through events as they happen. You will not get events from before you start listening */
  8. export class TypedEvent<T> {
  9. private listeners: Listener<T>[] = [];
  10. private listenersOncer: Listener<T>[] = [];
  11. on = (listener: Listener<T>): Disposable => {
  12. this.listeners.push(listener);
  13. return {
  14. dispose: () => this.off(listener)
  15. };
  16. }
  17. once = (listener: Listener<T>): void => {
  18. this.listenersOncer.push(listener);
  19. }
  20. off = (listener: Listener<T>) => {
  21. var callbackIndex = this.listeners.indexOf(listener);
  22. if (callbackIndex > -1) this.listeners.splice(callbackIndex, 1);
  23. }
  24. emit = (event: T) => {
  25. /** Update any general listeners */
  26. this.listeners.forEach((listener) => listener(event));
  27. /** Clear the `once` queue */
  28. this.listenersOncer.forEach((listener) => listener(event));
  29. this.listenersOncer = [];
  30. }
  31. pipe = (te: TypedEvent<T>): Disposable => {
  32. return this.on((e) => te.emit(e));
  33. }
  34. }