中间件可以说是Koa的核心了,可以方便我们对功能进行组合,这里我们看看在Koa中,中间件是如何实现的
使用
在Koa中,我们通过use函数注册中间件,中间件函数有两个参数ctx和next,分别是Koa的context对象和下一个中间件的next函数,基础使用如下:
const app = new Koa();
// 为了演示,构建响应数据
var responseData = {}
app.use(async (ctx,next) => {
responseData.name = 'Ethan'
ctx.body = responseData
await next() // 如果不调用next,则后续均不会执行
})
app.use(async (ctx,next) => {
responseData.sex = 'man'
ctx.body = responseData
await next()
})
app.use(async (ctx,next) => {
responseData.age = '24'
ctx.body = responseData
await next()
})
初探
根据使用方式大概可以知道app对象中应该有一个类似于middlewares的数组,用来存储所有的中间件函数,问题的关键是这些中间件函数并不是循环调用的,因为我们可以通过next决定后续中间件是否执行。
只关注中间件本身,忽视ctx函数,中间件大概长如下这样
async function m1(next) {
console.log('m1');
await next();
}
async function m2(next) {
console.log('m2');
await next();
}
async function m3(next) {
console.log('m3');
await next();
}
效果:m1通过调用next函数可以决定m2是否执行,m2通过是否调用next决定m3是否执行。很明显,我们需要构造一个next函数,调用m2和m3。
async function next2(){
await m2(next3)
}
async function next3(){
await m3()
}
m1(next2)
这样基本正常了,但是存在一个问题,执行到m3时,由于没有传递next函数,会报错,可是我们又没有中间件函数了,该如何处理呢,直接给定一个返回resolve的Promise。
async function last(){
return Promise.resolve();
}
async function next3(){
await m3(last)
}
在这里可以看出,我们需要一个createNext函数用来创建next函数,很显然我们需要参数middleware和next,具体如下:
function createNext(middleware,next){
return async function(){
await middleware(next)
}
}
封装
我们封装一个compose函数,执行后可以将所有middlewares串联起来,考虑到Koa中还有ctx参数,因此compose返回一个函数接收ctx参数,代码如下:
function compose(middlewares){
return ctx => {
function createNext(middleware,next){
return async function(){
await middleware(next)
}
}
// 最后的next
async function next(){
return Promise.resolve();
}
// 从后往前
for(var i = middlewares.length -1; i >= 0; i--){
next = createNext(middlewares[i],next)
}
await next()
}
}
总结
有没有发现compose函数应用场景是在很多,第一次接触是在redux的mini实现中,也是实现中间件机制,然后在函数式编程中也深有体会,这次在Koa中又碰到啦。
虽然碰到多次,但每次都有新感觉,感觉还是要更多的实践。
实践源码:mini-Koa