Typed Event Emitter

Naseebullah Ahmadi  Senior Software Engineer, London

A ~20-line pub/sub where the event names and each event's payload type come from one map, so `on` and `emit` are fully checked against it and `on` returns its own unsubscribe.

3 min read
#frontend

A string-keyed event bus is quick to write and just as quick to get wrong - emit('user:login', {}) with the wrong shape fails silently. Anchor the whole thing to one Record<eventName, payload> map instead, and on / emit type-check against it, with the payload narrowed per event.

@itsnas usage.ts
codeusage.ts
type AppEvents = {
  'user:login': { userId: string }
  'cart:add': { sku: string; quantity: number }
  toast: string
}
 
// @src/code/emitter
const bus = new Emitter<AppEvents>()
 
const stop = bus.on('user:login', ({ userId }) => {
  identify(userId) // payload is { userId: string }
})
 
bus.emit('cart:add', { sku: 'A-100', quantity: 2 })
 
bus.emit('cart:add', { sku: 'A-100' }) // ✗ missing `quantity`
bus.emit('user:logout', undefined) // ✗ not a key of AppEvents
 
stop() // unsubscribe
main
Nas (@itsnas)