-
-
Save darrenscerri/5c3b3dcbe4d370435cfa to your computer and use it in GitHub Desktop.
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 | |
}); |
Great job ! Here a modified version to pass arguments.
how to pass multiple functions with 'next' object? Like expressJS ?
Hi,
What refactoring should I do for this middleware in order to be able to pass (err, res) for each next in turn?
Meaning something like next(err, res)
so that at the end of the middleware stack execution I have some result object
Nice work! inspired me to make my own using typescript https://gist.github.com/Curtis017/ce072208f5160e8c24b77cc662f10fec
I hope you can write and explain detail it. Thank so much.
@darrenscerri Thanks for this nice piece of code, it helped me learn about call stacks and closures.
I just wanted to point out that this bind
is unnecessary since it's not used inside the bound function.
What was the intention behind using it anyway?
the "this" in this case refers to the global object, maybe you were trying to bind self
instead?
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
})
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);
}
}
nice!