《koa誕生記》——compose源碼從零解析

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)

洋蔥模型
中間件執(zhí)行過程

上面的代碼可以記錄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)的方式進行了連接:
fn = fn1(() => fn2(() => fn3(prev)))
對于 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對象。然后幽邓,通過 rejectcatch來進行錯誤處理炮温。

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包裹成下列形式:
fn = fn1(() => fn2(() => fn3(prev)))
那么柒啤,對于習(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)), () => {})();
}

引用

compose代碼解析

koa2 洋蔥模型

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
  • 序言:七十年代末没炒,一起剝皮案震驚了整個濱河市涛癌,隨后出現(xiàn)的幾起案子,更是在濱河造成了極大的恐慌,老刑警劉巖祖很,帶你破解...
    沈念sama閱讀 216,496評論 6 501
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件笛丙,死亡現(xiàn)場離奇詭異,居然都是意外死亡假颇,警方通過查閱死者的電腦和手機胚鸯,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 92,407評論 3 392
  • 文/潘曉璐 我一進店門,熙熙樓的掌柜王于貴愁眉苦臉地迎上來笨鸡,“玉大人姜钳,你說我怎么就攤上這事⌒魏模” “怎么了哥桥?”我有些...
    開封第一講書人閱讀 162,632評論 0 353
  • 文/不壞的土叔 我叫張陵,是天一觀的道長激涤。 經(jīng)常有香客問我拟糕,道長,這世上最難降的妖魔是什么倦踢? 我笑而不...
    開封第一講書人閱讀 58,180評論 1 292
  • 正文 為了忘掉前任送滞,我火速辦了婚禮,結(jié)果婚禮上辱挥,老公的妹妹穿的比我還像新娘犁嗅。我一直安慰自己,他們只是感情好晤碘,可當(dāng)我...
    茶點故事閱讀 67,198評論 6 388
  • 文/花漫 我一把揭開白布褂微。 她就那樣靜靜地躺著,像睡著了一般园爷。 火紅的嫁衣襯著肌膚如雪宠蚂。 梳的紋絲不亂的頭發(fā)上,一...
    開封第一講書人閱讀 51,165評論 1 299
  • 那天腮介,我揣著相機與錄音肥矢,去河邊找鬼。 笑死叠洗,一個胖子當(dāng)著我的面吹牛甘改,可吹牛的內(nèi)容都是我干的。 我是一名探鬼主播灭抑,決...
    沈念sama閱讀 40,052評論 3 418
  • 文/蒼蘭香墨 我猛地睜開眼十艾,長吁一口氣:“原來是場噩夢啊……” “哼!你這毒婦竟也來了腾节?” 一聲冷哼從身側(cè)響起忘嫉,我...
    開封第一講書人閱讀 38,910評論 0 274
  • 序言:老撾萬榮一對情侶失蹤荤牍,失蹤者是張志新(化名)和其女友劉穎,沒想到半個月后庆冕,有當(dāng)?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體康吵,經(jīng)...
    沈念sama閱讀 45,324評論 1 310
  • 正文 獨居荒郊野嶺守林人離奇死亡,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點故事閱讀 37,542評論 2 332
  • 正文 我和宋清朗相戀三年访递,在試婚紗的時候發(fā)現(xiàn)自己被綠了晦嵌。 大學(xué)時的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片。...
    茶點故事閱讀 39,711評論 1 348
  • 序言:一個原本活蹦亂跳的男人離奇死亡拷姿,死狀恐怖惭载,靈堂內(nèi)的尸體忽然破棺而出,到底是詐尸還是另有隱情响巢,我是刑警寧澤描滔,帶...
    沈念sama閱讀 35,424評論 5 343
  • 正文 年R本政府宣布,位于F島的核電站踪古,受9級特大地震影響含长,放射性物質(zhì)發(fā)生泄漏。R本人自食惡果不足惜灾炭,卻給世界環(huán)境...
    茶點故事閱讀 41,017評論 3 326
  • 文/蒙蒙 一茎芋、第九天 我趴在偏房一處隱蔽的房頂上張望颅眶。 院中可真熱鬧蜈出,春花似錦、人聲如沸涛酗。這莊子的主人今日做“春日...
    開封第一講書人閱讀 31,668評論 0 22
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽商叹。三九已至燕刻,卻和暖如春,著一層夾襖步出監(jiān)牢的瞬間剖笙,已是汗流浹背卵洗。 一陣腳步聲響...
    開封第一講書人閱讀 32,823評論 1 269
  • 我被黑心中介騙來泰國打工, 沒想到剛下飛機就差點兒被人妖公主榨干…… 1. 我叫王不留弥咪,地道東北人过蹂。 一個月前我還...
    沈念sama閱讀 47,722評論 2 368
  • 正文 我出身青樓,卻偏偏與公主長得像聚至,于是被迫代替她去往敵國和親酷勺。 傳聞我的和親對象是個殘疾皇子,可洞房花燭夜當(dāng)晚...
    茶點故事閱讀 44,611評論 2 353

推薦閱讀更多精彩內(nèi)容

  • 看到標(biāo)題扳躬,也許您會覺得奇怪脆诉,redux跟Koa以及Express并不是同一類別的框架甚亭,干嘛要拿來做類比。盡管击胜,例如...
    Perkin_閱讀 1,723評論 0 4
  • Koa源碼解析 整體架構(gòu) 核心文件只有4個亏狰,在lib文件夾下: application.js koa框架的入口...
    Ethan_lcm閱讀 2,433評論 0 1
  • 陸陸續(xù)續(xù)用了koa和co也算差不多用了大半年了,大部分的場景都是在服務(wù)端使用koa來作為restful服務(wù)器用偶摔,使...
    Sunil閱讀 1,544評論 0 3
  • 今天骚揍,成都五鳳溪古鎮(zhèn)閑逛,碧藍(lán)的天空如夢如幻啰挪。 碧空纖云舞 鏡湖翠柳染 秋陽如夏灸人臉 曉風(fēng)微拂起輕瀾
    小冰橙兒閱讀 176評論 0 0
  • 前一陣子信不,因為給自己定下了一個鍛煉身體的計劃,在體內(nèi)不明亢奮的因素影響下亡呵,第一周感覺非常好抽活,每天都有大量的多巴胺分...
    趙程沖閱讀 107評論 0 0