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;
}
}
本文对你有帮助?欢迎扫码加入前端学习小组微信群:
