Last active
October 27, 2021 01:44
-
-
Save jgermade/d6da7edabffd828d81f365605bd189a5 to your computer and use it in GitHub Desktop.
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
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 | |
} |
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 { 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