const errors = require('./lib/errors') class EventListener { constructor () { this.list = [] this.count = 0 } append (ctx, name, fn, once) { this.count++ ctx.emit('newListener', name, fn) // Emit BEFORE adding this.list.push([fn, once]) } prepend (ctx, name, fn, once) { this.count++ ctx.emit('newListener', name, fn) // Emit BEFORE adding this.list.unshift([fn, once]) } remove (ctx, name, fn) { for (let i = 0, n = this.list.length; i < n; i++) { const l = this.list[i] if (l[0] === fn) { this.list.splice(i, 1) if (this.count === 1) delete ctx._events[name] ctx.emit('removeListener', name, fn) // Emit AFTER removing this.count-- return } } } removeAll (ctx, name) { const list = [...this.list] this.list = [] if (this.count === list.length) delete ctx._events[name] for (let i = list.length - 1; i >= 0; i--) { ctx.emit('removeListener', name, list[i][0]) // Emit AFTER removing } this.count -= list.length } emit (ctx, name, ...args) { const list = [...this.list] for (let i = 0, n = list.length; i < n; i++) { const l = list[i] if (l[1] === true) this.remove(ctx, name, l[0]) l[0].call(ctx, ...args) } return list.length > 0 } } function appendListener (ctx, name, fn, once) { const e = ctx._events[name] || (ctx._events[name] = new EventListener()) e.append(ctx, name, fn, once) return ctx } function prependListener (ctx, name, fn, once) { const e = ctx._events[name] || (ctx._events[name] = new EventListener()) e.prepend(ctx, name, fn, once) return ctx } function removeListener (ctx, name, fn) { const e = ctx._events[name] if (e !== undefined) e.remove(ctx, name, fn) return ctx } function throwUnhandledError (...args) { let err if (args.length > 0) err = args[0] if (err instanceof Error === false) err = errors.UNHANDLED_ERROR(err) if (Error.captureStackTrace) { Error.captureStackTrace(err, exports.prototype.emit) } queueMicrotask(() => { throw err }) } module.exports = exports = class EventEmitter { constructor () { this._events = Object.create(null) } addListener (name, fn) { return appendListener(this, name, fn, false) } addOnceListener (name, fn) { return appendListener(this, name, fn, true) } prependListener (name, fn) { return prependListener(this, name, fn, false) } prependOnceListener (name, fn) { return prependListener(this, name, fn, true) } removeListener (name, fn) { return removeListener(this, name, fn) } on (name, fn) { return appendListener(this, name, fn, false) } once (name, fn) { return appendListener(this, name, fn, true) } off (name, fn) { return removeListener(this, name, fn) } emit (name, ...args) { if (name === 'error' && this._events.error === undefined) throwUnhandledError(...args) const e = this._events[name] return e === undefined ? false : e.emit(this, name, ...args) } listeners (name) { const e = this._events[name] return e === undefined ? [] : [...e.list] } listenerCount (name) { const e = this._events[name] return e === undefined ? 0 : e.list.length } getMaxListeners () { return EventEmitter.defaultMaxListeners } setMaxListeners (n) {} removeAllListeners (name) { if (arguments.length === 0) { for (const key of Reflect.ownKeys(this._events)) { if (key === 'removeListener') continue this.removeAllListeners(key) } this.removeAllListeners('removeListener') } else { const e = this._events[name] if (e !== undefined) e.removeAll(this, name) } return this } } exports.EventEmitter = exports exports.errors = errors exports.defaultMaxListeners = 10 exports.on = function on (emitter, name, opts = {}) { const { signal } = opts if (signal && signal.aborted) { throw errors.OPERATION_ABORTED(signal.reason) } let error = null let done = false const events = [] const promises = [] emitter.on(name, onevent) if (name !== 'error') emitter.on('error', onerror) if (signal) signal.addEventListener('abort', onabort) return { next () { if (events.length) { return Promise.resolve({ value: events.shift(), done: false }) } if (error) { const err = error error = null return Promise.reject(err) } if (done) return onclose() return new Promise((resolve, reject) => promises.push({ resolve, reject }) ) }, return () { return onclose() }, throw (err) { return onerror(err) }, [Symbol.asyncIterator] () { return this } } function onevent (...args) { if (promises.length) { promises.shift().resolve({ value: args, done: false }) } else { events.push(args) } } function onerror (err) { if (promises.length) { promises.shift().reject(err) } else { error = err } return Promise.resolve({ done: true }) } function onabort () { onerror(errors.OPERATION_ABORTED(signal.reason)) } function onclose () { emitter.off(name, onevent) if (name !== 'error') emitter.off('error', onerror) if (signal) signal.removeEventListener('abort', onabort) done = true if (promises.length) promises.shift().resolve({ done: true }) return Promise.resolve({ done: true }) } } exports.once = function once (emitter, name, opts = {}) { const { signal } = opts if (signal && signal.aborted) { throw errors.OPERATION_ABORTED(signal.reason) } return new Promise((resolve, reject) => { if (name !== 'error') emitter.on('error', onerror) if (signal) signal.addEventListener('abort', onabort) emitter.once(name, (...args) => { if (name !== 'error') emitter.off('error', onerror) if (signal) signal.removeEventListener('abort', onabort) resolve(args) }) function onerror (err) { emitter.off('error', onerror) reject(err) } function onabort () { signal.removeEventListener('abort', onabort) onerror(errors.OPERATION_ABORTED(signal.reason)) } }) } exports.forward = function forward (from, to, names, opts = {}) { if (typeof names === 'string') names = [names] const { emit = to.emit.bind(to) } = opts const listeners = names.map((name) => function onevent (...args) { emit(name, ...args) }) to .on('newListener', (name) => { const i = names.indexOf(name) if (i !== -1 && to.listenerCount(name) === 0) { from.on(name, listeners[i]) } }) .on('removeListener', (name) => { const i = names.indexOf(name) if (i !== -1 && to.listenerCount(name) === 0) { from.off(name, listeners[i]) } }) }