用过express,koa,react(redux)的都会知道中间件,那么在这三个框架的中间件的实现有什么不同呢?
Express中间件
首先来看看express,分析express的源码可以看出express中router中间件和express是强耦合的,express把中间件的实现放在了router中去。
大概讲一下这里的中间件实现逻辑:
- 通过app.use收集中间件到stacks中去
- 有客户端请求时,遍历执行所有的中间件(把下一个中间件作为上一个中间件的next传入)
以下是这个逻辑的简单实现:
var app = {
stacks: [],
// 收集中间件
use: function () {
var fns = arguments
for (var i = 0, l = fns.length; i < l; i++) {
this.stacks.push(fns[i])
}
},
// 触发中间件
handle: function (req, res) {
var idx = 0,
stacks = this.stacks;
next()
function next () {
if (idx === stacks.length) {
return;
}
var match = stacks[idx];
idx++;
match(req, res, next)
}
}
}
app.use(function (req, res, next) {
console.log(0)
next()
console.log('00')
}, function (req, res, next) {
console.log(1)
next()
console.log(11)
}, function () {
console.log(2)
})
app.handle()
Koa中间件
function compose (middleware) {
return function (context, next) {
// last called middleware #
let index = -1
return dispatch(0)
function dispatch (i) {
// 注释的部分为Koa源码,源码对async/await更加友好,为了简便对比,这里省略一些
// if (i <= index) return Promise.reject(new Error('next() called multiple times'))
index = i
let fn = middleware[i]
if (i === middleware.length) return Promise.resolve()
// if (!fn) return Promise.resolve()
// try {
// return Promise.resolve(fn(context, dispatch.bind(null, i + 1)));
// } catch (err) {
// return Promise.reject(err)
// }
return fn(context, dispatch.bind(null, i + 1))
}
}
}
var app = {
stacks: [],
use: function () {
var fns = arguments
for (var i = 0, l = fns.length; i < l; i++) {
this.stacks.push(fns[i])
}
},
listen: function (...args) {
// const server = http.createServer(this.callback())
// return server.listen(...args)
// 以下代码是为了方便调用中间件
var cb = this.callback()
cb()
},
callback: function () {
var fn = compose(this.stacks)
return fn
}
}
app.use(async function(ctx, next) {
console.log(1)
await next()
console.log(11)
}, async function (ctx, next) {
console.log(2)
await next()
console.log(22)
}, async function (ctx, next) {
console.log(3)
await next()
console.log(33)
})
app.listen()
中间件在Koa中的实现其实和Express没有本质的区别,逻辑基本上是一样的,多了一步compose,就是把中间件组合成一个函数。
说起Koa的中间件,很多地方都会提到洋葱模型,关于洋葱模型,按之前express的例子来说,就是按输出0,1,2,11,00的顺序执行,所以说洋葱模型并不是Koa特有的东西,另外,async/await也不是Koa才有的,async/await只是一个语法糖而已。
在我看来,Koa的中间件和Express的中间件区别在于Koa对异步中间件的处理可能更友好一些,从代码中的注释的源码部分可以看出。(对Koa没有实际的项目经验,仅就自己对源码的一些理解,有不对的地方希望可以不吝赐教)
Redux中间件
Redux中间件和这里使用的场景不同,这里我按Redux中间件的写法实现在node框架中的中间件。
function compose (middleware) {
return middleware.reduceRight((composed, f) => f(composed), () => {})
}
var app = {
stacks: [],
use: function () {
var fns = arguments
for (var i = 0, l = fns.length; i < l; i++) {
this.stacks.push(fns[i])
}
},
listen: function (...args) {
// const server = http.createServer(this.callback())
// return server.listen(...args)
var cb = this.callback()
cb()
},
callback: function () {
var ctx = {request: {}, response: {}}
var middlewares = this.stacks.map(middleware => middleware(ctx))
var fn = compose(middlewares)
return fn
}
}
app.use(ctx => next => () => {
console.log(1, ctx)
next()
console.log(11)
}, ctx => next => () => {
console.log(2, ctx)
next()
console.log(22)
}, ctx => next => () => {
console.log(3, ctx)
next()
console.log(33)
})
app.listen()
比较有意思的是compose函数的写法,利用es6,仅有一行代码就实现了。
这里是函数式编程中的currying,是一种使用匿名单参数函数实现多参数函数的方法。