koa中间件原理分析
先说一下啥是aop, aop 就是 Aspect Oriented Programming 面向切面编程,java中的spring最大的卖点就是ioc 和 aop, 然而好的东西大家都会去借鉴,应该说任何一个后端框架都有aop。koa的中间件原理被称为洋葱模型
aop有哪些好处
- 代码复用:封装成中间件,可以服用,没啥毛病
- 解耦:有了中间件,可以分层,没啥毛病
- 集中管理:中间件干一样的事情,各司其职,没啥毛病
分析
const Koa = require('koa')
const app = new Koa()
// logger
app.use(async (ctx,next) => {
await next()
const rt = ctx.response.get('X-Response-Time')
console.log(`${ctx.method} ${ctx.url} - ${rt}`)
})
// x-response-time
app.use(async (ctx,next) => {
const start = Date.now()
await next()
const ms = Date.now() - start
ctx.set('X-Response-Time', `${ms} ms`)
})
app.use(ctx => {
// console.log(ctx)
ctx.body = 'hello word'
})
app.listen(3000, () => {
console.log('run in 3000')
})
访问localhost:3000


这个好像看不出来什么
再来一个栗子
const Koa = require('koa')
const app = new Koa()
const middleware = async (ctx, next)=>{
console.log(1)
await next()
console.log(6)
}
const middleware2 = async (ctx, next)=> {
console.log(2)
await next()
console.log(5)
}
const middleware3 = async (ctx, next) => {
console.log(3)
await next()
console.log(4)
}
app.use(middleware)
app.use(middleware2)
app.use(middleware3)
app.use(ctx => {
ctx.body = 'xx'
})
app.listen(3000, ()=> {
console.log('run in 3000')
})


有点感觉了没
去翻一下源码

use(fn) {
if (typeof fn !== 'function') throw new TypeError('middleware must be a function!');
if (isGeneratorFunction(fn)) {
deprecate('Support for generators will be removed in v3. ' +
'See the documentation for examples of how to convert old middleware ' +
'https://github.com/koajs/koa/blob/master/docs/migration.md');
fn = convert(fn);
}
debug('use %s', fn._name || fn.name || '-');
this.middleware.push(fn);
return this;
}
从这里可以看出,调用use的时候会把中间件存起来,
下面我们来看看app.listen
listen(...args) {
debug('listen');
const server = http.createServer(this.callback());
return server.listen(...args);
}
listen 又调用了 callback,我们再来看看callback
callback() {
const fn = compose(this.middleware);
if (!this.listenerCount('error')) this.on('error', this.onerror);
const handleRequest = (req, res) => {
const ctx = this.createContext(req, res);
if (!this.ctxStorage) {
return this.handleRequest(ctx, fn);
}
return this.ctxStorage.run(ctx, async() => {
return await this.handleRequest(ctx, fn);
});
};
return handleRequest;
}
callback 把 中间件传进了compose 函数里边,然后构建了context,去看一下compose


function compose (middleware) {
return function (context, next) {
// last called middleware #
let index = -1
return dispatch(0)
function dispatch (i) {
index = i
let fn = middleware[i]
if (i === middleware.length) fn = next
if (!fn) return Promise.resolve()
try {
return Promise.resolve(fn(context, dispatch.bind(null, i + 1)));
} catch (err) {
return Promise.reject(err)
}
}
}
}
找到关键了, koa的中间件原理就是这个函数,把这个函数拿下,就能理解koa的洋葱模型了,这里的compose 函数,经过了简化,把一些错误校验去掉了,不影响核心逻辑
通过观察可以看出来,这是个递归,还是个尾递归,还是个异步递归
这段代码的核心在于next的实现。next需要依次调用下一个middleware,当到最后一个的时候结束,这样后面middleware的promise先resolve,然后直到第一个,这样的流程也就是洋葱模型的流程了
浙公网安备 33010602011771号