koa使用洋葱模型
koa是由Express原班人马打造的轻量级的node框架,koa使用中间件机制,避免了重复繁重的函数回调。其中间件的调用和执行,正是基于洋葱模型。下面代码,反映出koa是如何调用执行中间件的:
const Koa = require('koa')
const app = new Koa()
// 中间件A
app.use(async (ctx, next) => {
console.log('A start')
await next()
console.log('A end')
})
// 中间件B
app.use(async (ctx, next) => {
console.log('B start')
await next()
console.log('B end')
})
// 中间件C
app.use(async (ctx, next) => {
console.log('C start')
await next()
console.log('C end')
})
app.use(async (ctx, next) => {
console.log('hello')
ctx.response.body = 'hello'
})
app.listen(3000)
上面的代码片段,node执行并在浏览器打开urlhttp://localhost:3000/后,控制台打印的结果是: A start -> B start -> C start -> hello -> C end -> B end -> A end 。其执行过程,像极了洋葱圈,所以被称为“洋葱模型”,每个洋葱圈是一个中间件,
中间件就是异步函数,其两个参数含义如下:
- ctx,koa的执行上下文,是对node的request和response的封装
- next,返回值是Promise的函数,触发下一个中间件的进入和执行
koa的每个请求的执行过程,即中间件的加载和执行,不同中间件可处理不同的事务;中间件的执行,有下面特点
- 遇到
await next(),立即进入下一个中间件; - 所有的中间件,已经被“进入过”,再回过头来执行next;
- “先进后出”,先进入的中间件,next是后执行的;
koa的洋葱模型是如何实现的?
- app.use,是加载和保存一个中间件。所以,可以使用一个数组保存所有已经被加载的中间件;
- koa-compose模块,实现了所有中间件的调度执行;遇到next(),即进入下一个中间件;所有中间件都已进入,再执行next的结果
let _app = {}
_app.middleware = []
_app.use = function (fn) {
this.middleware.push(fn)
}
function compose(middleware) {
function dispatch(index) {
let fn = middleware[index]
if(!fn) {
return Promise.resolve()
}
try {
return Promise.resolve(fn(() => {
return dispatch(index + 1)
}))
} catch(err) {
return Promise.reject(err)
}
}
return dispatch(0)
}
// 测试
_app.use(async (next) => {
console.log('111')
await next()
console.log('111 end')
})
_app.use(async (next) => {
console.log('222')
await next()
console.log('222 end')
})
compose(_app.middleware)
为方便演示洋葱模型的执行逻辑,这里的中间件实现,并没有加入ctx;
Promise.resolve(fn(() => { return dispatch(index+1) })),使得中间件函数fn立即执行;- fn中间件的执行,当遇到
await next(),会触发dispatch(index+1),至此,新的中间件开始执行,重复步骤1; - 当middleware数组被遍历执行完,程序终止;