使用中间件模式,让代码和 ExpressJS, ReduxJS 一样容易扩展

938 阅读1分钟
原文链接: github.com
Powerful Javascript Middleware Pattern Implementation, apply middleweare to any object.
'use strict';

let applyMiddlewareHash = [];

/**
 * Composes single-argument functions from right to left. The rightmost
 * function can take multiple arguments as it provides the signature for
 * the resulting composite function.
 *
 * @param {...Function} funcs The functions to compose.
 * @returns {Function} A function obtained by composing the argument functions
 * from right to left. For example, compose(f, g, h) is identical to doing
 * (...args) => f(g(h(...args))).
 */
export function compose(...funcs) {
  if (funcs.length === 0) {
    return arg => arg;
  }

  funcs = funcs.filter(func => typeof func === 'function');

  if (funcs.length === 1) {
    return funcs[0];
  }

  const last = funcs[funcs.length - 1];
  const rest = funcs.slice(0, -1);
  return (...args) => rest.reduceRight((composed, f) => f(composed), last(...args));
}

/**
 * Apply middleweare to an object.
 * Middleware functions are functions that have access to the target function and it's arguments,
 * and the target object and the next middleware function in the target function cycle.
 * The next middleware function is commonly denoted by a variable named next.
 *
 * Middleware functions can perform the following tasks:
 *  - Execute any code.
 *  - Make changes to the function's arguments.
 *  - End the target function.
 *  - Call the next middleware in the stack.
 *
 * If the current middleware function does not end the target function cycle,
 * it must call next() to pass control to the next middleware function. Otherwise,
 * the target function will be left hanging.
 *
 * e.g.
 *  ```
 *  const walk = target => next => (...args) => {
 *     this.log(`walk function start.`);
 *     const result = next(...args);
 *     this.log(`walk function end.`);
 *     return result;
 *   }
 *  ```
 *
 * Middleware object is an object that contains function's name as same as the target object's function name.
 *
 * e.g.
 *  ```
 *  const Logger = {
 *      walk: target => next => (...args) => {
 *        console.log(`walk function start.`);
 *        const result = next(...args);
 *        console.log(`walk function end.`);
 *        return result;
 *      }
 *   }
 *  ```
 *
 * Function's name start or end with "_" will not be able to apply middleware.
 *
 * @example
 *
 * // the target object
 * class Person {
 *  // the target function
 *  walk(step) {
 *    this.step = step;
 *  }
 * }
 *
 * // middleware for walk function
 * const logger = target => next => (...args) => {
 *    this.log(`walk start, steps: ${args[0]}.`);
 *    const result = next(...args);
 *    this.log(`walk end.`);
 *    return result;
 *  }
 *
 * // apply middleware to target object
 * const p = new Person();
 * const applyMiddleware = new ApplyMiddleware(p);
 * applyMiddleware.use('walk', walk);
 * p.walk();
 *
 */
export class ApplyMiddleware {
  /**
   * @param {object} target The target object.
   * @param {...object} middlewareObjects Middleware objects.
   * @return {object} this
   */
  constructor(target, ...middlewareObjects) {
    const that = this;
    let instance = applyMiddlewareHash.find(function (key) {
      return key._target === target;
    });
    // a target can only has one ApplyMiddleware instance
    if (instance === undefined) {
      this._target = target;
      this._methods = {};
      this._methodMiddlewares = {};
      applyMiddlewareHash.push(this);
      instance = that;
    }
    instance.use(...middlewareObjects);

    return instance;
  }

  _applyToMethod(methodName, ...middlewares) {
    if (typeof methodName === 'string' && !/^_+|_+$/g.test(methodName)) {
      let method = this._methods[methodName] || this._target[methodName];
      if (typeof method === 'function') {
        this._methods[methodName] = method;
        if (this._methodMiddlewares[methodName] === undefined) {
          this._methodMiddlewares[methodName] = [];
        }
        middlewares.forEach(middleware =>
          typeof middleware === 'function' && this._methodMiddlewares[methodName].push(middleware(this._target))
        );
        this._target[methodName] = compose(...this._methodMiddlewares[methodName])(method.bind(this._target));
      }
    }
  }

  /**
   * Apply middleware to the target object.
   * @param {string|object} methodName String for target function name, object
   * @param {...function} middlewares The middleware chain to be applied.
   * @return {object} this
   */
  use(methodName, ...middlewares) {
    if (typeof methodName === 'object') {
      Array.prototype.slice.call(arguments).forEach(arg => {
        // A middleweare object can specify targer functions within middlewareMethods (Array).
        // e.g. obj.middlewareMethods = ['method1', 'method2'];
        // only method1 and method2 will be the target function.
        typeof arg === 'object' && (arg.middlewareMethods || Object.keys(arg)).forEach(key => {
          this._applyToMethod(key, arg[key].bind(arg));
        });
      });
    } else {
      this._applyToMethod(methodName, ...middlewares);
    }

    return this;
  }
}

本文对你有帮助?欢迎扫码加入前端学习小组微信群: