Koa is a new web framework designed by the team behind Express, which aims to be a smaller, more expressive, and more robust foundation for web applications and APIs. By leveraging async functions, Koa allows you to ditch callbacks and greatly increase error-handling. Koa does not bundle any middleware within its core, and it provides an elegant suite of methods that make writing servers fast and enjoyable.
上面是koa的官網(wǎng)的簡單介紹,只需要關(guān)心一點: 中間件機制是koa的核心瓷翻。
可以說聚凹,理解了中間件也就理解了koa框架的精華。而實現(xiàn)中間件機制的關(guān)鍵是compose函數(shù)齐帚。
洋蔥模型的基本介紹
每個中間件需要依次處理request和response請求妒牙。這種中間件模型稱為洋蔥模型(Onion model)
上面的代碼可以記錄response請求的時間《酝可以看到湘今,利用koa實現(xiàn)logger
,代碼相當(dāng)簡潔剪菱。
compose 1.0 版本實現(xiàn)
五年前摩瞎,前端沒有async的情況下,compose
的實現(xiàn)其實相當(dāng)復(fù)雜孝常,利用了Thunk旗们、generator、Co
來進行異步管理构灸。不過上渴,可以看到即使前端變化非常之大,compose
的核心理念依然沒有發(fā)生改變。
-
不考慮任何異步情況稠氮,實現(xiàn)洋蔥模型
function fn1(next) {
console.log(1);
next();
}
function fn2(next) {
console.log(2);
next();
}
function fn3(next) {
console.log(3);
next();
}
middleware = [fn1, fn2, fn3]
function compose(middleware){
function dispatch (index){
if(index == middleware.length) return ;
var curr;
curr = middleware[index];
// 這里使用箭頭函數(shù)曹阔,讓函數(shù)延遲執(zhí)行
return curr(() => dispatch(++index))
}
dispatch(0)
};
compose(middleware);
根據(jù)分析,最后實際上將幾個函數(shù)通過串聯(lián)的方式進行了連接:
對于 fn1
來說隔披,next函數(shù)就是 ()=> fn2( () => fn3())
-
考慮無promise的異步情況赃份。(callback+generator)
當(dāng)出現(xiàn)generator類型的時候,我們next允許接受Generator類型
function * fn1(next) {
console.log(1);
//如果沒有yield奢米,就無法進行遞歸調(diào)用
yield next();
}
function * fn2(next) {
console.log(2);
yield next();
}
function * fn3(next) {
console.log(3);
yield next();
}
middleware = [fn1, fn2, fn3]
function compose(middleware){
function dispatch (index){
if(index == middleware.length) return ;
var curr;
function* prev(){
console.log('none');
}
curr = middleware[index]
console.log(curr);
return curr(() => dispatch(++index))
}
return dispatch(0)
};
compose(middleware)
這時候運行抓韩,compose(middleware)
實際上是一個 [GeneratorFunction fn1]
的類型。
如果我們需要達(dá)到第一種代碼的運行效果恃慧,手動執(zhí)行如下:
k0 = compose(middleware).next()
k1 = k0.value
k2 = k1.next().value
k3 = k2.next()
//輸出為1 2 3
中間件多的話园蝠,手動執(zhí)行就無法實現(xiàn)渺蒿×蹬模可以增加一個自動執(zhí)行g(shù)enerator的函數(shù):
function co (gen) {
let g = gen;
function next(nex) {
let result = nex.next();
if(result.done) return result.value;
if(typeof result.value == 'object') {
next(result.value);
}
}
next(g);
}
//再次執(zhí)行, 輸出為123
co(compose(middleware))
generator+co的方式實現(xiàn)中間件代碼邏輯相當(dāng)復(fù)雜遭笋,上面只是考慮了三種情況下的一種。
compose 2.0 版本實現(xiàn)
-
利用promise實現(xiàn)
function compose(middleware){
function dispatch (index){
if(index == middleware.length) return ;
var curr;
function prev(){
next;
}
curr = middleware[index];
// 這里使用箭頭函數(shù),讓函數(shù)延遲執(zhí)行
return curr(() => dispatch(++index))
}
dispatch(0)
};
當(dāng)異步操作使用 async/await
的時候铸磅,上面compose
的實現(xiàn)已經(jīng)可以解決異步問題。(async
函數(shù)可以看作同步函數(shù))幻妓。但是疆偿,異步操作代碼,如果拋出錯誤彼妻,上面的代碼無法對錯誤進行捕捉嫌佑。
function * fn1(next) {
console.log(1);
throw new Error('錯誤無法捕捉');
//如果沒有yield,就無法進行遞歸調(diào)用
yield next();
}
考慮到侨歉,async其實返回一個Promise類型屋摇,我們將所有的中間件函數(shù)包裹成一個Promise對象。然后幽邓,通過 reject
和 catch
來進行錯誤處理炮温。
function compose(middleware){
function dispatch (index){
if(index == middleware.length) return Promise.resolve();
var curr;
function prev(){
next;
}
curr = middleware[index];
// 修改成Promise對象
return Promise.resolve(curr(() => dispatch(++index)));
}
dispatch(0)
};
-
函數(shù)式風(fēng)格實現(xiàn)
從上面的實現(xiàn)我們可以看出來,以上所有的實現(xiàn)牵舵,都無非是把中間件函數(shù) fn1
, fn2
, fn3
包裹成下列形式:
那么柒啤,對于習(xí)慣使用函數(shù)式編程的人來說,這其實是一個右向reduce的過程畸颅。
function compose () {
return this.middlewares.reduceRight( (a, b) => () => b(a), () => {})();
}
然后担巩,如果需要修改返回類型是Promise類型,那么可以簡單的修改為:
function compose () {
return this.middlewares.reduceRight( (a, b) => () => Promise.resolve(b(a)), () => {})();
}