上一講我們講到 koa-router 的實現,今天我們講講 koa-compose格郁,compose是將多個函數合并成一個函數(形如: g() + h() => g(h())
),koa-compose則是將 koa/koa-router 各個中間件合并執(zhí)行独悴,結合 next() 就形成了洋蔥式模型
有同學可能不了解為何是洋蔥式模型例书,接下來我們改造下官方test用例,打出相應 log 應該就清楚了
it.only('should work', async () => {
const arr = []
const stack = []
stack.push(async (context, next) => {
arr.push(1)
await wait(1)
console.log(1)
await next()
console.log(1, 1)
await wait(1)
arr.push(6)
})
stack.push(async (context, next) => {
arr.push(2)
await wait(1)
console.log(2)
await next()
console.log(2, 2)
await wait(1)
arr.push(5)
})
stack.push(async (context, next) => {
arr.push(3)
await wait(1)
console.log(3)
await next()
console.log(3, 3)
await wait(1)
arr.push(4)
})
const a = compose(stack)
console.log('result-->>>>>>', a)
await compose(stack)({})
expect(arr).toEqual(expect.arrayContaining([1, 2, 3, 4, 5, 6]))
})
結果是1, 2, 3, 33, 22, 11
刻炒,可以看出先按順序執(zhí)行 stack 數組中每個中間件 next 前的代碼决采,之后倒序執(zhí)行 next 后的代碼,相當于一根牙簽橫穿洋蔥坟奥,先從外到內织狐,再從內到外,所以叫做洋蔥模型筏勒。
koa-compose 的代碼只有不夠50行移迫,細讀確實是一段很精妙的代碼,而實際核心代碼則是這一段:
return 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
if (!fn) return Promise.resolve()
try {
return Promise.resolve(fn(context, function next () {
return dispatch(i + 1)
}))
} catch (err) {
return Promise.reject(err)
}
}
}
雖然短管行,但是之中使用了4層 return
厨埋,初看會比較繞,我們只看第3捐顷,4層 return荡陷,這是返回實際的執(zhí)行代碼鏈
return Promise.resolve(fn(context, function next () {
return dispatch(i + 1)
}))
以下分解參照跨入Koa2.0雨效,從Compose開始這篇文章,說得挺好的废赞。
這里嘗試用3個中間件進行 compose徽龟,并逐步分解執(zhí)行過程
- 第一次,此時第一個中間件被調用唉地,dispatch(0)据悔,展開:
Promise.resolve(function(context, next){
//中間件一第一部分代碼
await/yield next();
//中間件一第二部分代碼
}());
很明顯這里的next指向dispatch(1)耘沼,那么就進入了第二個中間件极颓;
- 第二次,此時第二個中間件被調用群嗤,dispatch(1)菠隆,展開:
Promise.resolve(function(context, 中間件2){
//中間件一第一部分代碼
await/yield Promise.resolve(function(context, next){
//中間件二第一部分代碼
await/yield next();
//中間件二第二部分代碼
}())
//中間件一第二部分代碼
}());
很明顯這里的next指向dispatch(2),那么就進入了第三個中間件狂秘;
- 第三次骇径,此時第二個中間件被調用,dispatch(2)者春,展開:
Promise.resolve(function(context, 中間件2){
//中間件一第一部分代碼
await/yield Promise.resolve(function(context, 中間件3){
//中間件二第一部分代碼
await/yield Promise(function(context){
//中間件三代碼
}());
//中間件二第二部分代碼
})
//中間件一第二部分代碼
}());
此時中間件三代碼執(zhí)行完畢既峡,開始執(zhí)行中間件二第二部分代碼,執(zhí)行完畢碧查,開始執(zhí)行中間一第二部分代碼,執(zhí)行完畢校仑,所有中間件加載完畢忠售。
總結
koa-compose 巧妙的運用了 compose 的特性,結合 async await 中 next 的等待執(zhí)行迄沫,形成了洋蔥模型稻扬,我們可以利用這一特性在 next 之前對 request 進行處理,而在 next 之后對 response 進行處理(例如 error 處理)