Last active
October 9, 2023 10:57
-
-
Save emkis/b275ddbb56052f9a353c05e0e055baac to your computer and use it in GitHub Desktop.
Simple TypeScript implementation of the event emitter pattern
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
import { createEventEmitter } from './event-emitter' | |
type ExampleEvents = { | |
add_todo: { id: string; text: string } | |
complete_todo: { id: string } | |
delete_todo: { id: string } | |
} | |
beforeEach(jest.clearAllMocks) | |
it('should only call registered event', () => { | |
const onAddTodo = jest.fn() | |
const todoEventEmitter = createEventEmitter<ExampleEvents>() | |
todoEventEmitter.on('add_todo', onAddTodo) | |
expect(onAddTodo).not.toHaveBeenCalled() | |
todoEventEmitter.emit('add_todo', { id: '1', text: 'Foo' }) | |
todoEventEmitter.emit('complete_todo', { id: '1' }) | |
todoEventEmitter.emit('delete_todo', { id: '1' }) | |
expect(onAddTodo).toHaveBeenCalledTimes(1) | |
expect(onAddTodo).toHaveBeenLastCalledWith({ id: '1', text: 'Foo' }) | |
}) | |
it('should not call handler after un-registering', () => { | |
const onCompleteTodo = jest.fn() | |
const todoEventEmitter = createEventEmitter<ExampleEvents>() | |
todoEventEmitter.on('complete_todo', onCompleteTodo) | |
todoEventEmitter.emit('complete_todo', { id: '2' }) | |
expect(onCompleteTodo).toHaveBeenCalledTimes(1) | |
expect(onCompleteTodo).toHaveBeenLastCalledWith({ id: '2' }) | |
todoEventEmitter.off('complete_todo', onCompleteTodo) | |
todoEventEmitter.emit('complete_todo', { id: '3' }) | |
todoEventEmitter.emit('complete_todo', { id: '4' }) | |
expect(onCompleteTodo).toHaveBeenCalledTimes(1) | |
}) | |
it('should allow registering multiple handlers', () => { | |
const handlerA = jest.fn() | |
const handlerB = jest.fn() | |
const todoEventEmitter = createEventEmitter<ExampleEvents>() | |
todoEventEmitter.on('delete_todo', handlerA) | |
todoEventEmitter.on('delete_todo', handlerB) | |
todoEventEmitter.emit('delete_todo', { id: '5' }) | |
expect(handlerA).toHaveBeenCalledTimes(1) | |
expect(handlerB).toHaveBeenCalledTimes(1) | |
}) |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
export type EventType = string; | |
export type EventsShape = Record<EventType, unknown>; | |
export type EventHandler<T> = (event: T) => void; | |
export type EventHandlerArray<T> = Array<EventHandler<T>>; | |
export type EventEmitter<Events extends EventsShape> = { | |
on: <TEventKey extends keyof Events>( | |
event: TEventKey, | |
handler: EventHandler<Events[TEventKey]>, | |
) => void; | |
off: <TEventKey extends keyof Events>( | |
event: TEventKey, | |
handler: EventHandler<Events[TEventKey]>, | |
) => void; | |
emit: <TEventKey extends keyof Events>(event: TEventKey, payload: Events[TEventKey]) => void; | |
}; | |
export function createEventEmitter<Events extends EventsShape>(): EventEmitter<Events> { | |
const listeners = new Map<keyof Events, EventHandlerArray<Events[keyof Events]>>(); | |
return { | |
on(event, listener) { | |
const _listener = listener as EventHandler<Events[keyof Events]>; | |
const eventListeners = listeners.get(event); | |
eventListeners ? eventListeners.push(_listener) : listeners.set(event, [_listener]); | |
}, | |
off(event, listener) { | |
const eventListeners = listeners.get(event); | |
if (!eventListeners) return; | |
const listenerIndex = eventListeners.indexOf(listener as EventHandler<Events[keyof Events]>); | |
const isListenerRegistered = listenerIndex !== -1; | |
if (isListenerRegistered) { | |
eventListeners.splice(listenerIndex, 1); | |
} | |
}, | |
emit(event, options) { | |
const eventListeners = listeners.get(event); | |
if (!eventListeners) return; | |
eventListeners.forEach((listener) => listener(options)); | |
}, | |
}; | |
} |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment