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