Skip to content
Type an event emitter

    ↑↓ move · ⏎ open · esc close

    Patterns

    How do I keep each event name paired with the right payload and listener?

    Use an event map and one key parameter so the event name selects the payload type in both methods.

    The recipe

    type Handler<Events, K extends keyof Events> =
      (payload: Events[K]) => void
    
    type TypedEmitter<Events extends object> = {
      on<K extends keyof Events>(event: K, handler: Handler<Events, K>): void
      emit<K extends keyof Events>(event: K, payload: Events[K]): void
    }
    
    type Events = {
      saved: { id: string }
      failed: { message: string }
    }
    
    declare const bus: TypedEmitter<Events>
    
    bus.on('saved', payload => payload.id)
    bus.emit('failed', { message: 'offline' })

    The build compiles this and checks each result below.

    How it works

    1. 01
      K extends keyof Events

      The key can only be a declared event name.

    2. 02
      payload: Events[K]

      Indexed access turns that chosen name into its exact payload.

    What you get

    Where it goes wrong

    The type only protects calls that pass through this interface. A loosely typed emitter underneath can still deliver malformed runtime data, so validate events that cross process or network boundaries.

    Takeaway

    A map plus `K extends keyof Map` is the standard way to keep a name correlated with its data.

    See also

    Patterns