Skip to content

Instantly share code, notes, and snippets.

@darrenscerri
Last active July 11, 2023 02:59
Show Gist options
  • Save darrenscerri/5c3b3dcbe4d370435cfa to your computer and use it in GitHub Desktop.
Save darrenscerri/5c3b3dcbe4d370435cfa to your computer and use it in GitHub Desktop.
A very minimal Javascript (ES5 & ES6) Middleware Pattern Implementation
var Middleware = function() {};
Middleware.prototype.use = function(fn) {
var self = this;
this.go = (function(stack) {
return function(next) {
stack.call(self, function() {
fn.call(self, next.bind(self));
});
}.bind(this);
})(this.go);
};
Middleware.prototype.go = function(next) {
next();
};
class Middleware {
use(fn) {
this.go = (stack => next => stack(fn.bind(this, next.bind(this))))(this.go);
}
go = next => next();
}
// Inspired by: https://github.com/kolodny/exercises/tree/master/middleware
var middleware = new Middleware();
middleware.use(function(next) {
var self = this;
setTimeout(function() {
self.hook1 = true;
next();
}, 10);
});
middleware.use(function(next) {
var self = this;
setTimeout(function() {
self.hook2 = true;
next();
}, 10);
});
var start = new Date();
middleware.go(function() {
console.log(this.hook1); // true
console.log(this.hook2); // true
console.log(new Date() - start); // around 20
});
@aronanda
Copy link

aronanda commented Sep 14, 2020

Well done, it took me a bit to "wrap" my head around.
Here's my version with parameters, optional object definition and you can pipe the use() method:
https://gist.github.com/aronanda/d31bb47918145a5aace6005f172e035d

class Middleware {
  constructor(obj) {
    obj = obj || this
    Object.defineProperty(this, '__obj', { value: obj })
    Object.defineProperty(this, 'go', { value: function go(...args) {
      args[args.length - 1].apply(obj, args.slice(0, -1))
    }, writable: true })
  }
  use(fn) {
    this.go = (stack => (...args) => stack(...args.slice(0, -1), () =>
      fn.call(this.__obj, ...args.slice(0, -1), args[args.length - 1]
        .bind(this.__obj, ...args.slice(0, -1)))))(this.go)
    return this
  }
}

// Example:
const middleware = new Middleware(/* define object or defaults to this */)

middleware.use(function (req, res, next) {
  setTimeout(() => {
    this.hook1 = true
    req.step++
    res.step++
    next()
  }, 10)
})

middleware.use(function (req, res, next) {
  setTimeout(() => {
    this.hook2 = true
    req.step++
    res.step++
    next()
  }, 10)
}).use((req, res, next) => {
  // chainable
  setTimeout(() => {
    req.step++
    res.step++
    next()
  }, 10)
})

const start = new Date()
const req = Object.create(new class Req {}(), { step: { value: 1, enumerable: true, writable: true }})
const res = Object.create(new class Res {}(), { step: { value: 1, enumerable: true, writable: true }})
middleware.go(req, res, function (req, res) {
  console.log(this) // Middleware { hook1: true, hook2: true }
  console.log(req) // Req { step: 4 }
  console.log(res) // Res { step: 4 }
  console.log(new Date() - start) // around 30ms
})

@yejinjian
Copy link

Inspired by you,I update my pipeline.js,like this:

util.js

/**
 * 数据去重
 * @param arr
 * @returns {any[]}
 */
export function uniq(arr) {
    return [...new Set(arr)];
}

/**
 * 数组合并去重
 * @param arr
 * @returns {*[]}
 */
export function union(...arr){
    return uniq(arr.reduce((a,b)=> a.concat(b),[]))
}

pipeline.js

import {union} from './utils';


/**
 * 核心
 * @param ctx
 * @param next
 */
const defGo = (ctx, next) => next(ctx);
/**
 * 类似koa-compose方法
 * @param tasks
 * @param dispatch
 * @param context
 * @returns {*}
 */
export const compose = (tasks, dispatch = defGo, context) =>{
    return tasks.reduce((stack,valve) => async (ctx,next)=> await valve(ctx, stack.bind(context,ctx,next)), dispatch);
}

/**
 * 管道模式
 */
export default class Pipeline {
    #valves = [] //中间件
    #basic = (e) => e
    #go = defGo

    constructor(valves = []) {
        this.addValve(...valves);
    }

    /**
     * 最终执行函数
     * @param basic
     */
    setBasic(basic) {
        if (typeof basic !== 'function') {
            throw new Error('basic must be a function');
        }
        this.#basic = basic;
        return this;
    }

    /**
     * 添加阀门
     * @param valves
     * @returns {boolean}
     */
    addValve(...valves) {
        //合并去重
        valves = valves.filter((valve) => typeof valve === 'function');
        this.#valves = union(this.#valves, valves);
        this.#go = compose(valves, this.#go);
        return this;
    }

    /**
     * 删除valves
     */
    clearValve(){
        this.#go = defGo;
        this.#valves = [];
        return this;
    }

    /**
     * 删除阀门
     * @param valves
     */
    removeValve(...valves){
        this.#valves = this.#valves.filter((valve)=> !valves.includes(valve));
        this.#go = compose(this.#valves, defGo);
        return this;
    }

    /**
     * 执行管道中的内容
     * @param params
     * @param basic
     * @returns {*}
     */
    invoke(params, basic) {
        if(basic  && typeof basic === 'function'){
            this.#basic = basic;
        }
        return this.#go(params, this.#basic);
    }
}

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