koa 的中间件机制巧妙的运用了闭包和 async await
的特点,形成了一个洋葱式的流程,和 JS 的事件流 (捕获 -> target -> 冒泡) 相似
handleRequest(ctx, fnMiddleware) {
const res = ctx.res;
res.statusCode = 404;
const onerror = err => ctx.onerror(err);
const handleResponse = () => respond(ctx);
onFinished(res, onerror);
return fnMiddleware(ctx).then(handleResponse).catch(onerror);
}
上述代码是 request 事件的句柄,也就是说每一个请求到来,都会执行这个总方法
- onerror 为请求设置了错误处理的方法
- handleResponse 是当中间件完成后给浏览器返回 response 的方法,里面是原生的
res.end(body)
- onFinished 是判断请求最终有没有完成,根据不同的结果采取不同的策略
-
fnMiddleware(ctx)
就是执行所有中间件函数,然后返回一个 Promise 对象,不出错的话执行handleResponse
洋葱式的中间件
值得一提的是,中间件原理的代码并没有放在 koa 中,而是单独打了一个模块,叫做 ==koa-compose==
function (context, next) {
// last called middleware #
let index = -1
return dispatch(0)
function dispatch (i) {
if (i <= index) return Promise.reject(new Error('next() called multiple times'))
index = i
let fn = middleware[i]
if (i === middleware.length) fn = next
// 返回给 next()
if (!fn) return Promise.resolve()
try {
// 返回给 next(),最外一层返回给 fnMiddleware(ctx).then(handleResponse)
return Promise.resolve(fn(context, function next () {
// 返回给外一层 fn 的 await
return dispatch(i + 1)
}))
} catch (err) {
return Promise.reject(err)
}
}
}
- 执行一次 dispatch 就是执行一个中间件,算是洋葱的一层
- 每个 dispatch 都会返回一个 Promise.resolve 给外面一层的 await(除了第一次,他返回给的是
fnMiddleware(ctx).then(handleResponse)
) - 每个 dispatch 都有一个自己的序号,也就是参数 i (他用闭包控制住了) ,从 0 开始
- 闭包里有一个
index
,是记录执行过的中间件数量。一旦有序号大于数量,说明有中间件执行了两次await next
,这是不被允许的 - 每一层用 Promise.resolve 包裹是因为 await 需要接收一个 Promise 对象
下面就是中间件原理的展开写法,仔细琢磨吧
function dispatch(0){ // 第一层的序号
return Promise.resolve(async function a0(){
cnosole.log('0-0')
await 111(function next0(){
return (function dispatch(1){ // 第二层的序号
return Promise.resolve(async function a1(){
cnosole.log('1-0')
await 222(function next1(){
return (function dispatch(2){ // 第三层的序号
return Promise.resolve(async function a2(){
cnosole.log('2-0')
await 333(function next2(){
return (function dispatch(3){ // i == middleware.length ,算是洋葱芯吧
// fn[3] == undefined,说明中间件已经到洋葱的最里面了,开始向外返回
return Promise.resolve()
})()
})()333
console.log('2-1')
})
})()
})()222
console.log('1-1')
})
})()
})()111
console.log('0-1')
})
}
dispatch(0).then(handleResponse)
思考
1. 普通函数采用 dispatch 算法也能取得洋葱式的流程,为何要使用 async ?
app.use(async function (ctx,next) {
console.log('1-1')
await new Promise(function(resolve, reject){
setTimeout(function () {
console.info
("wait for 10 mini seconds.");
resolve();
},10);
});
console.log('1-2')
next();
console.log('1-3')
})
app.use(async function (ctx,next) {
console.log('2-1')
await new Promise(function(resolve){
setTimeout(function () {
console.info
("wait for 10 mini seconds");
resolve();
},10);
});
console.log('2-2')
next();
console.log('2-3')
})
试试 next() 前面加上 await 和不加 await 的区别就明白了
2. 为何要用 Promise.resolve 返回
因为他是洋葱式的层级,如果用普通的 Boolean 返回的话,只能返回到上一层,没法全局获取,对错误的把控难以控制。Promise 任何一层报错,都能用 catch 捕获
总结
koa 是一个非常轻量级的框架,只实现了中间件处理流程和对 res、req
对象的封装。其他的功能都由外部中间件提供。代码不是很多,但是很精妙,对于代码能力的提高有不小的帮助
END
**粗体** _斜体_ [链接](http://example.com) `代码` - 列表 > 引用
。你还可以使用@
来通知其他用户。