const errors = require('./lib/errors') class EventListener { constructor () { this.list = [] } append (ctx, name, fn, once) { ctx.emit('newListener', name, fn) // Emit BEFORE adding this.list.push([fn, once]) } prepend (ctx, name, fn, once) { 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.list.length === 0) delete ctx._events[name] ctx.emit('removeListener', name, fn) // Emit AFTER removing return } } } removeAll (ctx, name) { const list = [...this.list] this.list = [] for (let i = list.length - 1; i >= 0; i--) { ctx.emit('removeListener', name, list[i][0]) // Emit AFTER removing } if (this.list.length === 0) delete ctx._events[name] } 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 } 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) { 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.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 (signal) signal.addEventListener('abort', onabort) emitter.once(name, (...args) => { if (signal) signal.removeEventListener('abort', onabort) resolve(args) }) function onabort () { reject(errors.OPERATION_ABORTED(signal.reason)) } }) }