|
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) |
|
this.list.push([fn, once]) |
|
} |
|
|
|
prepend (ctx, name, fn, once) { |
|
this.count++ |
|
ctx.emit('newListener', name, fn) |
|
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) |
|
|
|
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]) |
|
} |
|
|
|
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.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)) |
|
} |
|
}) |
|
} |
|
|