index.js•7.04 kB
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])
      }
    })
}
exports.listenerCount = function listenerCount (emitter, name) {
  return emitter.listenerCount(name)
}