Source: EventEmitter.js

/**
 * Representation of a single EventEmitter function.
 * @private
 */
class EE {
  /**
   * @param {function} fn Event handler to be called.
   * @param {Mixed} context Context for function execution.
   * @param {boolean} once Only emit once
   * @private
   */
  constructor(fn, context, once = false) {
    this.fn = fn;
    this.context = context;
    this.once = once;
  }
}

/**
 * Minimal EventEmitter interface that is molded against the Node.js
 * EventEmitter interface.
 *
 * @class EventEmitter
 */
class EventEmitter {
  /**
   * @constructor
   */
  constructor() {
    /**
     * Holds the assigned EventEmitters by name.
     *
     * @type {object}
     * @private
     */
    this._events = undefined;
  }

  /**
   * Return a list of assigned event listeners.
   *
   * @memberof EventEmitter#
   * @method listeners
   * @param {string} event The events that should be listed.
   * @param {boolean} exists We only need to know if there are listeners.
   * @returns {array|boolean} Listener list
   */
  listeners(event, exists) {
    let available = this._events && this._events[event];

    if (exists) {return !!available;}
    if (!available) {return [];}
    if (available.fn) {return [available.fn];}

    let i, l, ee;
    for (i = 0, l = available.length, ee = new Array(l); i < l; i++) {
      ee[i] = available[i].fn;
    }

    return ee;
  }

  /**
   * Emit an event to all registered event listeners.
   *
   * @memberof EventEmitter#
   * @method emit
   * @param {string} event  The name of the event.
   * @param {*} a1          First param
   * @param {*} a2          Second param
   * @param {*} a3          Third param
   * @param {*} a4          Forth param
   * @param {*} a5          Fifth param
   * @returns {boolean} Indication if we've emitted an event.
   */
  emit(event, a1, a2, a3, a4, a5) {
    if (!this._events || !this._events[event]) {return false;}

    let listeners = this._events[event],
      len = arguments.length, args, i;

    if ('function' === typeof listeners.fn) {
      if (listeners.once) {this.removeListener(event, listeners.fn, undefined, true);}

      switch (len) {
        case 1: return listeners.fn.call(listeners.context), true;
        case 2: return listeners.fn.call(listeners.context, a1), true;
        case 3: return listeners.fn.call(listeners.context, a1, a2), true;
        case 4: return listeners.fn.call(listeners.context, a1, a2, a3), true;
        case 5: return listeners.fn.call(listeners.context, a1, a2, a3, a4), true;
        case 6: return listeners.fn.call(listeners.context, a1, a2, a3, a4, a5), true;
      }

      for (i = 1, args = new Array(len - 1); i < len; i++) {
        args[i - 1] = arguments[i];
      }

      listeners.fn.apply(listeners.context, args);
    }
    else {
      let length = listeners.length, j;

      for (i = 0; i < length; i++) {
        if (listeners[i].once) {this.removeListener(event, listeners[i].fn, undefined, true);}

        switch (len) {
          case 1: listeners[i].fn.call(listeners[i].context); break;
          case 2: listeners[i].fn.call(listeners[i].context, a1); break;
          case 3: listeners[i].fn.call(listeners[i].context, a1, a2); break;
          default:
            if (!args) {
              for (j = 1, args = new Array(len - 1); j < len; j++) {
                args[j - 1] = arguments[j];
              }}

            listeners[i].fn.apply(listeners[i].context, args);
        }
      }
    }

    return true;
  }

  /**
   * Register a new EventListener for the given event.
   *
   * @memberof EventEmitter#
   * @method on
   * @param {string} event Name of the event.
   * @param {functon} fn Callback function.
   * @param {Mixed} context The context of the function.
   * @return {EventEmitter} Self for chaining
   */
  on(event, fn, context) {
    var listener = new EE(fn, context || this);

    if (!this._events) {this._events = Object.create(null);}
    if (!this._events[event]) {this._events[event] = listener;}
    else {
      if (!this._events[event].fn) {this._events[event].push(listener);}
      else {
        this._events[event] = [
          this._events[event], listener,
        ];}
    }

    return this;
  }

  /**
   * Add an EventListener that's only called once.
   *
   * @memberof EventEmitter#
   * @method once
   * @param {string} event Name of the event.
   * @param {function} fn Callback function.
   * @param {Mixed} context The context of the function.
   * @return {EventEmitter} Self for chaining
   */
  once(event, fn, context) {
    let listener = new EE(fn, context || this, true);

    if (!this._events) {this._events = Object.create(null);}
    if (!this._events[event]) {this._events[event] = listener;}
    else {
      if (!this._events[event].fn) {this._events[event].push(listener);}
      else {
        this._events[event] = [
          this._events[event], listener,
        ];}
    }

    return this;
  }

  /**
   * Remove event listeners.
   *
   * @memberof EventEmitter#
   * @method removeListener
   * @param {string} event The event we want to remove.
   * @param {function} fn The listener that we need to find.
   * @param {Mixed} context Only remove listeners matching this context.
   * @param {boolean} once Only remove once listeners.
   * @return {EventEmitter} Self for chaining
   */
  removeListener(event, fn, context, once) {
    if (!this._events || !this._events[event]) {return this;}

    let listeners = this._events[event], events = [];

    if (fn) {
      if (listeners.fn) {
        if (
             listeners.fn !== fn
          || (once && !listeners.once)
          || (context && listeners.context !== context)
        ) {
          events.push(listeners);
        }
      }
      else {
        for (let i = 0, length = listeners.length; i < length; i++) {
          if (
               listeners[i].fn !== fn
            || (once && !listeners[i].once)
            || (context && listeners[i].context !== context)
          ) {
            events.push(listeners[i]);
          }
        }
      }
    }

    //
    // Reset the array, or remove it completely if we have no more listeners.
    //
    if (events.length) {
      this._events[event] = events.length === 1 ? events[0] : events;
    }
    else {
      delete this._events[event];
    }

    return this;
  }

  /**
   * Remove all listeners or only the listeners for the specified event.
   *
   * @memberof EventEmitter#
   * @method removeAllListeners
   * @param {string} event The event want to remove all listeners for.
   * @return {EventEmitter} Self for chaining
   */
  removeAllListeners(event) {
    if (!this._events) {return this;}

    if (event) {delete this._events[event];}
    else {this._events = Object.create(null);}

    return this;
  }
}
/**
 * @method off
 * @memberof EventEmitter#
 * @alias EventEmitter#removeListener
 */
EventEmitter.prototype.off = EventEmitter.prototype.removeListener;
/**
 * @method addListener
 * @memberof EventEmitter#
 * @alias EventEmitter#on
 */
EventEmitter.prototype.addListener = EventEmitter.prototype.on;


/**
 * Minimal EventEmitter interface that is molded against the Node.js
 * EventEmitter interface.
 *
 * @exports engine/EventEmitter
 * @see EventEmitter
 */
module.exports = EventEmitter;