Home Manual Reference Source Test Repository

lib/plugin/event-handler.js

import _ from 'lodash';

export default class EventHandler {
  _handlers = {};

  _reset() {
    this._handlers = {};
  }

  _getHandlers(eventName) {
    this._handlers[eventName] = this._handlers[eventName] || [];
    return this._handlers[eventName];
  }

  addEventHandler(eventName, handler) {
    this._getHandlers(eventName).push(handler);
  }

  /**
   * Processes our queue of event handlers for a given eventName.
   * If a handler returns 'undefined' then we give the next handler
   * the original arguments so that every handler gets to touch the
   * data.
   * @param {string} eventName Event key where the handlers live.
   * @param {*} args Arguments.
   * @return {Promise} Promise that resolves to the ending value of the
   *   promise chain.
   */
  async processEventHandlers(eventName, ...args) {
    const handlers = this._getHandlers(eventName);

    let processedValue = args;
    // eslint-disable-next-line no-plusplus
    for (let i = 0; i < handlers.length; i++) {
      const handler = handlers[i];
      let newVal;

      // We need the returned value so we can give it to the next event handler.
      // Effectively a waterfall.
      // eslint-disable-next-line no-await-in-loop
      newVal = await handler(...processedValue);

      // We allow a plugin to return undefined or null to implicitly
      // allow us to use the original value that was passed into the
      // plugin handler.
      if (_.isUndefined(newVal) || newVal === null) {
        newVal = processedValue;
      }

      // If our original value was an array of 1 and is now not an
      // array because a plugin just returned the value stick it back
      // into an array to keep its type consistent.
      if (processedValue.length === 1 && !Array.isArray(newVal)) {
        newVal = [newVal];
      }

      if (newVal.length !== processedValue.length) {
        throw new Error(`Plugin handler for '${eventName}' did not` +
          ' return all arguments given to it.');
      } else {
        // eslint-disable-next-line no-loop-func
        const allTypesMatch = newVal.every((val, index) =>
           typeof val === typeof processedValue[index]
        );

        if (!allTypesMatch) {
          throw new Error(`Plugin handler for '${eventName}' did not` +
            ' return all arguments in given order.');
        }
      }

      processedValue = newVal;
    }

    // If we have only one value then unpack it.
    return processedValue.length === 1 ?
      processedValue[0] :
      processedValue;
  }
}