Skip to content

Instantly share code, notes, and snippets.

@unbug
Last active January 6, 2024 04:17
  • Star 60 You must be signed in to star a gist
  • Fork 15 You must be signed in to fork a gist
Star You must be signed in to star a gist
Save unbug/dd596d79b5eace7d245f0a4db6cd2be5 to your computer and use it in GitHub Desktop.
Powerful Javascript Middleware Pattern Implementation, apply middleweares to any object. https://unbug.github.io/js-middleware/
'use strict';
/* eslint-disable consistent-this */
let middlewareManagerHash = [];
/**
* 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));
}
/**
* Manage and apply middlewares for 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
*
* ## Basic
*
* We define a Person class.
* // the target object
* class Person {
* // the target function
* walk(step) {
* this.step = step;
* }
*
* speak(word) {
* this.word = word;
* }
* }
*
* Then we define a middleware function to print log.
*
* // middleware for walk function
* const logger = target => next => (...args) => {
* console.log(`walk start, steps: ${args[0]}.`);
* const result = next(...args);
* console.log(`walk end.`);
* return result;
* }
*
* Now we apply the log function as a middleware to a Person instance.
*
* // apply middleware to target object
* const p = new Person();
* const middlewareManager = new MiddlewareManager(p);
* middlewareManager.use('walk', walk);
* p.walk(3);
*
* Whenever a Person instance call it's walk method, we'll see logs from the looger middleware.
*
* ## Middleware object
* We can also apply a middleware object to a target object.
* Middleware object is an object that contains function's name as same as the target object's function name.
*
* const PersonMiddleware {
* walk: target => next => step => {
* console.log(`walk start, steps: step.`);
* const result = next(step);
* console.log(`walk end.`);
* return result;
* },
* speak: target => next => word => {
* word = 'this is a middleware trying to say: ' + word;
* return next(word);
* }
* }
*
* // apply middleware to target object
* const p = new Person();
* const middlewareManager = new MiddlewareManager(p);
* middlewareManager.use(PersonMiddleware);
* p.walk(3);
* p.speak('hi');
*
* ## middlewareMethods
* Or we can use `middlewareMethods` to define function names for middleware target within a class.
*
* class CuePointMiddleware {
* constructor() {
* //Define function names for middleware target.
* this.middlewareMethods = ['walk', 'speak'];
* }
* log(text) {
* console.log('Middleware log: ' + text);
* }
* walk(target) {
* return next => step => {
* this.log(`walk start, steps: step.`);
* const result = next(step);
* this.log(`walk end.`);
* return result;
* }
* }
* speak(target) {
* return next => word => {
* this.log('this is a middleware tring to say: ' + word);
* return next(word);
* }
* }
* }
*
* // apply middleware to target object
* const p = new Person();
* const middlewareManager = new MiddlewareManager(p);
* middlewareManager.use(new PersonMiddleware())
* p.walk(3);
* p.speak('hi');
*
*/
export class MiddlewareManager {
/**
* @param {object} target The target object.
* @param {...object} middlewareObjects Middleware objects.
* @return {object} this
*/
constructor(target, ...middlewareObjects) {
let instance = middlewareManagerHash.find(function (key) {
return key._target === target;
});
// a target can only has one MiddlewareManager instance
if (instance === undefined) {
this._target = target;
this._methods = {};
this._methodMiddlewares = {};
middlewareManagerHash.push(this);
instance = this;
}
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 (register) middleware functions to the target function or apply (register) middleware objects.
* If the first argument is a middleware object, the rest arguments must be middleware objects.
*
* @param {string|object} methodName String for target function name, object for a middleware object.
* @param {...function|...object} 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 middleware object can specify target 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 => {
typeof arg[key] === 'function' && this._applyToMethod(key, arg[key].bind(arg));
});
});
} else {
this._applyToMethod(methodName, ...middlewares);
}
return this;
}
}
@unbug
Copy link
Author

unbug commented Dec 21, 2016

js-middleware

Powerful Javascript Middleware Pattern implementation, apply middleweares to any object.
A painless solution to make codes as scalable and maintainable as ReduxJS and ExpressJS.

Links

Overview

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.

Get started

  1. window.MiddlewareManager is available for browsers by include
    dist/middleware.min.js file in your HTML.
  <script src="middleware.min.js"></script>
  1. Or install the package
npm install --save js-middleware

and import it in your files

import {MiddlewareManager} from 'js-middleware';

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment