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