Skip to content

Usage Patterns

Compose emitter instances

ts
const emitterA = new EventEmitter<{ ready: [void] }>()
const emitterB = new EventEmitter<{ ready: [void] }>()

const bridge = (event: 'ready') => emitterA.on(event, (...args) => emitterB.emit(event, ...args))
bridge('ready')

Use this to forward events between modules or to expose a single emitter API backed by multiple internal sources.

Integrate with DOM APIs

ts
const ui = new EventEmitter<{ click: [MouseEvent], close: [void] }>()

document.addEventListener('click', (evt) => ui.emit('click', evt))
document.querySelector('[data-close]')?.addEventListener('click', () => ui.emit('close'))

Scoped teardown

ts
const emitter = new EventEmitter<{ change: [string] }>()
const listeners = [
  () => emitter.on('change', (key) => console.log('first', key)),
  () => emitter.once('change', (key) => console.log('second', key)),
]

export function mount() {
  listeners.forEach((register) => register())
}

export function unmount() {
  emitter.clearListeners()
}

Async workflows

ts
const emitter = new EventEmitter<{ done: [string], error: [Error] }>()

export const runTask = async (task: () => Promise<string>) => {
  try {
    const result = await task()
    emitter.emit('done', result)
  } catch (err) {
    emitter.emit('error', err as Error)
  }
}

Namespacing events

ts
type Namespaced<T extends string> = `${T}:${string}`

const emitter = new EventEmitter<Record<Namespaced<'graph' | 'ui'>, [unknown]>>()

type GraphEvent = `graph:${string}`
emitter.on('graph:ready', () => ...)

More real-world scenarios? Extend this file with contributions that show how to bridge frameworks, log listener counts, or debug stuck events.

Built with VitePress – Released under the MIT License.