Skip to content

Instantly share code, notes, and snippets.

@kawanet
Created September 21, 2026 13:29
Show Gist options
  • Select an option

  • Save kawanet/3e2e7d17fbedb2c534fd1805b713ae83 to your computer and use it in GitHub Desktop.

Select an option

Save kawanet/3e2e7d17fbedb2c534fd1805b713ae83 to your computer and use it in GitHub Desktop.
Minimalist EventEmitter-like event handler
export interface Events<T extends Record<string, unknown[]>> {
emit<K extends StringKeyOf<T>>(event: K, ...args: T[K]): boolean
off<K extends StringKeyOf<T>>(event: K, listener: (...args: T[K]) => void): unknown
on<K extends StringKeyOf<T>>(event: K, listener: (...args: T[K]) => void): unknown
once<K extends StringKeyOf<T>>(event: K, listener: (...args: T[K]) => void): unknown
}
type StringKeyOf<T> = Extract<keyof T, string>
type Fn = <A extends unknown[]>(...args: A) => void
export const createEvents = <T extends Record<string, unknown[]>>(): Events<T> => {
const registry: Record<StringKeyOf<T>, Fn[]> = Object.create(null)
const emit: Events<T>["emit"] = (event, ...args) => {
const listeners = registry[event]
listeners?.slice().forEach(fn => fn(...args))
return !!listeners?.length
}
const off: Events<T>["off"] = (event, listener) => {
const listeners = registry[event]
if (!listeners) return
registry[event] = listeners.filter(fn => (listener == null || fn !== listener))
}
const on: Events<T>["on"] = (event, listener) => {
const listeners = (registry[event] ??= []) as (typeof listener)[]
listeners.push(listener)
}
const once: Events<T>["once"] = (event, listener) => {
const eraser: Fn = () => {
off(event, eraser)
off(event, listener)
}
on(event, eraser)
on(event, listener)
}
return {emit, off, on, once}
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment