Skip to content

Instantly share code, notes, and snippets.

@jgermade
Last active October 27, 2021 01:44
Show Gist options
  • Save jgermade/d6da7edabffd828d81f365605bd189a5 to your computer and use it in GitHub Desktop.
Save jgermade/d6da7edabffd828d81f365605bd189a5 to your computer and use it in GitHub Desktop.
const resolved = Promise.resolve()
const nextTick = fn => resolved.then(fn)
const _runListeners = (thisArg, args, listeners, runAfter = fn => fn()) => {
listeners?.forEach(listener => runAfter(() => listener.apply(thisArg, args)))
}
export function EventEmitter (context = null) {
const _events = {}
const _events_once = {}
const _events_any = []
if (context && this instanceof EventEmitter) {
throw new Error('EventEmitter can not receive context when creating (new) instance')
}
if (!context && !(this instanceof EventEmitter)) {
throw new Error('EventEmitter requires context when NOT creating new instance')
}
const _context = context || this
_context.on = (eventName, listener) => {
if (!_events[eventName]) _events[eventName] = []
const index = _events[eventName].indexOf(listener)
if (index >= 0) throw new Error(`Event '${eventName}' already defined`)
_events[eventName].push(listener)
return _context
}
_context.once = (eventName, listener) => {
if (!_events_once[eventName]) _events_once[eventName] = []
const index = _events_once[eventName].indexOf(listener)
if (index >= 0) throw new Error(`Event '${eventName}' already defined`)
_events_once[eventName].push(listener)
return _context
}
_context.off = (eventName, listener) => {
if (_events[eventName]) {
const index = _events[eventName].indexOf(listener)
if (index >= 0) _events[eventName].splice(index, 1)
}
if (_events_once[eventName]) {
const index_once = _events_once[eventName].indexOf(listener)
if (index_once >= 0) _events_once[eventName].splice(index_once, 1)
}
return _context
}
_context.onAny = (listener) => {
_events_any.push(listener)
return _context
}
_context.offAny = (listener) => {
const index = _events_any.indexOf(listener)
if (index >= 0) _events_any.splice(index, 1)
return _context
}
_context.emitSync = (eventName, ...args) => {
_runListeners(_context, args, _events[eventName])
_runListeners(_context, args, _events_once[eventName]?.splice(0))
_runListeners(_context, args, _events_any)
return true
}
_context.emit = (eventName, ...args) => {
return new Promise(resolve => {
nextTick(() => {
_runListeners(_context, args, _events[eventName], nextTick)
_runListeners(_context, args, _events_once[eventName]?.splice(0), nextTick)
_runListeners(_context, args, _events_any, nextTick)
nextTick(resolve)
})
})
}
return _context
}
import { EventEmitter } from './EventEmitter'
const resolvedPromise = Promise.resolve()
const nextTick = (fn = () => {}) => resolvedPromise.then(fn)
describe('EventEmitter', () => {
test('on:emit(\'foo\')', async () => {
const EE = new EventEmitter()
const listener = jest.fn()
EE.on('foo', listener)
EE.emit('foo', 'bar')
await nextTick()
expect(listener.mock.calls.length).toBe(1)
expect(listener.mock.calls[0][0]).toBe('bar')
EE.emit('foo', 'foo')
await nextTick()
expect(listener.mock.calls.length).toBe(2)
expect(listener.mock.calls[1][0]).toBe('foo')
})
test('once:emit(\'foo\')', async () => {
const EE = new EventEmitter()
const listener = jest.fn()
EE.once('foo', listener)
EE.emit('foo', 'bar')
await nextTick()
expect(listener.mock.calls.length).toBe(1)
expect(listener.mock.calls[0][0]).toBe('bar')
EE.emit('foo', 'foo')
await nextTick()
expect(listener.mock.calls.length).toBe(1)
})
test('on:emit:off(\'foo\')', async () => {
const EE = new EventEmitter()
const listener = jest.fn()
EE.on('foo', listener)
EE.emit('foo', 'bar')
await nextTick()
expect(listener.mock.calls.length).toBe(1)
expect(listener.mock.calls[0][0]).toBe('bar')
EE.off('foo', listener)
EE.emit('foo', 'foo')
await nextTick()
expect(listener.mock.calls.length).toBe(1)
})
test('on:off:emit(\'foo\')', async () => {
const EE = new EventEmitter()
const listener = jest.fn()
EE.on('foo', listener)
EE.off('foo', listener)
EE.emit('foo', 'foo')
await nextTick()
expect(listener.mock.calls.length).toBe(0)
})
test('once:off:emit(\'foo\')', async () => {
const EE = new EventEmitter()
const listener = jest.fn()
EE.once('foo', listener)
EE.off('foo', listener)
EE.emit('foo', 'foo')
await nextTick()
expect(listener.mock.calls.length).toBe(0)
})
})
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment