koa
的中间件模型,通常称作洋葱模型。
在 koa
的源码中,koa
中间件串联是基于koa-compose。
我们可以自定义代码来模拟实现洋葱模型:
js
const app = {
middlewares: [],
use (fn) {
this.middlewares.push(fn)
},
compose (middlewares) {
let index = 0
// 返回函数
return function (ctx) {
// dispatch是一个返回promise的函数
function dispatch (index) {
// middleware是一个返回promise的函数
const middleware = middlewares[index]
// next也是一个返回promise的函数
const next = index < middlewares.length - 1 ? () => dispatch(++index) : () => Promise.resolve()
return middleware(ctx, next)
}
// 返回promise
return dispatch(index)
}
},
listen (port) {
const promiseFn = this.compose(this.middlewares)
const ctx = {
query: {
name: 'test'
},
body: 'Hello'
}
promiseFn(ctx).then(_ => {
console.log(`app listening on port ${port}`)
}).catch(err => {
console.error('err', err)
})
}
}
app.use(async (ctx, next) => {
console.log('middleware 1')
await next()
console.log('middleware 1 after')
})
app.use(async (ctx, next) => {
console.log('middleware 2')
await next()
console.log('middleware 2 after')
})
app.use(async (ctx, next) => {
console.log('async')
await next()
await new Promise((resolve) => {
setTimeout(() => {
console.log('async end in 3s')
resolve()
}, 3000)
})
})
app.use(async (ctx, next) => {
console.log('middleware 3')
await next()
console.log('middleware 3 after')
})
app.listen(3000)
上述代码执行结果为:
zsh
middleware 1
middleware 2
async
middleware 3
middleware 3 after
async end in 3s
middleware 2 after
middleware 1 after
app listening on port 3000
TIP
关于 koa
与 express
的中间件模型,其实大致是一样的。
譬如 koa
和 express
都是基于 next
函数来构建中间件执行链,且顺序都是 input1 => next2 => input2 => output2 => outpu1
。
但由于 koa
是基于 promise
构建的中间件执行链,因此它对于异步任务的处理方式,要比 express
更加友好。
上述结论,我们可以通过比较 koa
与 express
的 demo
代码执行结果,来理解。