Koa中間件(middleware)實(shí)現(xiàn)探索

說起Node,最常用的估計(jì)就是express和koa,兩者都用到了中間件(middleware)這一概念惨远,主要用于對(duì)請(qǐng)求的統(tǒng)一處理。

koa的請(qǐng)求處理是典型的洋蔥模型,下面是官方的配圖话肖,而這一模型的組成部分就是middleware


接下來我們來看一下koa的源碼北秽,了解中間件的實(shí)現(xiàn)方式。

首先我們找到了koa的倉(cāng)庫(kù)Koa,好吧,我知道你們都會(huì)這一步最筒。

package.json中找到模塊的入口文件application.js,稍微瀏覽一下(不得不說贺氓,tj大神的代碼寫的真的漂亮)就可以找到Koa處理請(qǐng)求的代碼

app.callback = function(){
  if (this.experimental) {
    console.error('Experimental ES7 Async Function support is deprecated. Please look into Koa v2 as the middleware signature has changed.')
  }
  var fn = this.experimental
    ? compose_es7(this.middleware)
    : co.wrap(compose(this.middleware));
  var self = this;
  if (!this.listeners('error').length) this.on('error', this.onerror);

  return function handleRequest(req, res){
    res.statusCode = 404;
    var ctx = self.createContext(req, res);
    onFinished(res, ctx.onerror);
    fn.call(ctx).then(function handleResponse() {
      respond.call(ctx);
    }).catch(ctx.onerror);
  }
};

看完這個(gè)函數(shù),我們了解到真正處理請(qǐng)求內(nèi)容的函數(shù)是fn,而這個(gè)函數(shù)的定義就是下面這段函數(shù)

var fn = this.experimental
    ? compose_es7(this.middleware)
    : co.wrap(compose(this.middleware));

好吧床蜘,準(zhǔn)確來說就是

co.wrap(compose(this.middleware));

其實(shí)我們只需要知道這段函數(shù)做了什么辙培,就知道中間件是如何運(yùn)行的了。

同時(shí)邢锯,我們?cè)?code>appliction.js文件找到了app.use函數(shù)

app.use = function(fn){
  ...
  
  this.middleware.push(fn);
  return this;
};

從這段代碼扬蕊,我們可以知道this.middleware就是一個(gè)generator函數(shù)的數(shù)組。

接下來我們需要知道compose函數(shù)做了什么丹擎,我們找到compose函數(shù)尾抑,其實(shí)compose很短

function compose(middleware){
  return function *(next){
    if (!next) next = noop();

    var i = middleware.length;

    while (i--) {
      next = middleware[i].call(this, next);
    }

    return yield *next;
  }
}

function *noop(){}

這里compose函數(shù)返回了一個(gè)Generator,所以上面的代碼可以變成下面的樣子(當(dāng)然還有middleware的變量再閉包中)

co.wrap(function *(next){
    if (!next) next = noop();

    var i = middleware.length;

    while (i--) {
      next = middleware[i].call(this, next);
    }

    return yield *next;
  })

接下來我們看一下co.wrap

co.wrap = function (fn) {
  createPromise.__generatorFunction__ = fn;
  return createPromise;
  function createPromise() {
    return co.call(this, fn.apply(this, arguments));
  }
};

所以代碼可以變?yōu)橄旅娴臉幼?/p>

function createPromise() {
    var fn = function *(next){
        if (!next) next = noop();
    
        var i = middleware.length;
    
        while (i--) {
          next = middleware[i].call(this, next);
        }
    
        return yield *next;
      } 
    return co.call(this, fn.apply(this, arguments));
  }

接下來蒂培,我們仔細(xì)看一下這段代碼

if (!next) next = noop();
    
var i = middleware.length;
    
while (i--) {
    next = middleware[i].call(this, next);
}

這段代碼遍歷了我們的中間件數(shù)組再愈,最終生成了一個(gè)類似下面的代碼

next = (function*(){
    // middleware1
    ...
    
    yield (function*(){
        // middleware2
        ...
        
        yield (function*(){
            // middleware3
            ...
            
            yield (function *(){
                // noop
                // NO next yield !
            })()
            
            // ...middleware3
        })
        
        // ...middleware2
    })
    // ...middleware1
})()

其實(shí)看到這里就已經(jīng)可以看到洋蔥模型的樣子了。

而最后就是這個(gè)next的運(yùn)行毁渗,其實(shí)這個(gè)next就是一個(gè)Generator函數(shù)生成的迭代器(iterator)對(duì)象践磅,然后由co來運(yùn)行,類似下面

co(function*(){
    ...

    yield *next
})

co可以對(duì)generator的進(jìn)行自執(zhí)行灸异。到這基本就完成可中間件的實(shí)現(xiàn)府适。

眼尖的讀者可以看到這里最后用到了yield *而非yield,可以有關(guān)于co的執(zhí)行,其實(shí)就是為了減少co的一次運(yùn)行肺樟,其實(shí)每次都應(yīng)該用yield *next,可能是tj大神怕大家忘記加了檐春,就索性在demo里面就建議大家直接yield next就好了。具體的可以看我對(duì)于co源碼實(shí)現(xiàn)的分析么伯,我這里就提一下yield *可以自動(dòng)執(zhí)行后面的表達(dá)式的迭代器屬性疟暖,而yield只會(huì)直接返回后面的表達(dá)式,所以一個(gè)yield *可以使co直接拿到后面迭代器中的每一步,而yield只可以拿到迭代器俐巴,然后遞歸調(diào)用co來執(zhí)行迭代器骨望。

最后歡迎大家吐槽,謝謝欣舵。

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
  • 序言:七十年代末擎鸠,一起剝皮案震驚了整個(gè)濱河市,隨后出現(xiàn)的幾起案子缘圈,更是在濱河造成了極大的恐慌劣光,老刑警劉巖,帶你破解...
    沈念sama閱讀 217,277評(píng)論 6 503
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件糟把,死亡現(xiàn)場(chǎng)離奇詭異绢涡,居然都是意外死亡,警方通過查閱死者的電腦和手機(jī)遣疯,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 92,689評(píng)論 3 393
  • 文/潘曉璐 我一進(jìn)店門雄可,熙熙樓的掌柜王于貴愁眉苦臉地迎上來,“玉大人缠犀,你說我怎么就攤上這事滞项。” “怎么了夭坪?”我有些...
    開封第一講書人閱讀 163,624評(píng)論 0 353
  • 文/不壞的土叔 我叫張陵,是天一觀的道長(zhǎng)过椎。 經(jīng)常有香客問我室梅,道長(zhǎng),這世上最難降的妖魔是什么疚宇? 我笑而不...
    開封第一講書人閱讀 58,356評(píng)論 1 293
  • 正文 為了忘掉前任亡鼠,我火速辦了婚禮,結(jié)果婚禮上敷待,老公的妹妹穿的比我還像新娘间涵。我一直安慰自己,他們只是感情好榜揖,可當(dāng)我...
    茶點(diǎn)故事閱讀 67,402評(píng)論 6 392
  • 文/花漫 我一把揭開白布勾哩。 她就那樣靜靜地躺著,像睡著了一般举哟。 火紅的嫁衣襯著肌膚如雪思劳。 梳的紋絲不亂的頭發(fā)上,一...
    開封第一講書人閱讀 51,292評(píng)論 1 301
  • 那天妨猩,我揣著相機(jī)與錄音潜叛,去河邊找鬼。 笑死,一個(gè)胖子當(dāng)著我的面吹牛威兜,可吹牛的內(nèi)容都是我干的销斟。 我是一名探鬼主播,決...
    沈念sama閱讀 40,135評(píng)論 3 418
  • 文/蒼蘭香墨 我猛地睜開眼椒舵,長(zhǎng)吁一口氣:“原來是場(chǎng)噩夢(mèng)啊……” “哼蚂踊!你這毒婦竟也來了?” 一聲冷哼從身側(cè)響起逮栅,我...
    開封第一講書人閱讀 38,992評(píng)論 0 275
  • 序言:老撾萬榮一對(duì)情侶失蹤悴势,失蹤者是張志新(化名)和其女友劉穎,沒想到半個(gè)月后措伐,有當(dāng)?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體特纤,經(jīng)...
    沈念sama閱讀 45,429評(píng)論 1 314
  • 正文 獨(dú)居荒郊野嶺守林人離奇死亡,尸身上長(zhǎng)有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點(diǎn)故事閱讀 37,636評(píng)論 3 334
  • 正文 我和宋清朗相戀三年侥加,在試婚紗的時(shí)候發(fā)現(xiàn)自己被綠了捧存。 大學(xué)時(shí)的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片。...
    茶點(diǎn)故事閱讀 39,785評(píng)論 1 348
  • 序言:一個(gè)原本活蹦亂跳的男人離奇死亡担败,死狀恐怖昔穴,靈堂內(nèi)的尸體忽然破棺而出,到底是詐尸還是另有隱情提前,我是刑警寧澤吗货,帶...
    沈念sama閱讀 35,492評(píng)論 5 345
  • 正文 年R本政府宣布,位于F島的核電站狈网,受9級(jí)特大地震影響宙搬,放射性物質(zhì)發(fā)生泄漏。R本人自食惡果不足惜拓哺,卻給世界環(huán)境...
    茶點(diǎn)故事閱讀 41,092評(píng)論 3 328
  • 文/蒙蒙 一勇垛、第九天 我趴在偏房一處隱蔽的房頂上張望。 院中可真熱鬧士鸥,春花似錦闲孤、人聲如沸。這莊子的主人今日做“春日...
    開封第一講書人閱讀 31,723評(píng)論 0 22
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽(yáng)。三九已至脚仔,卻和暖如春币砂,著一層夾襖步出監(jiān)牢的瞬間,已是汗流浹背玻侥。 一陣腳步聲響...
    開封第一講書人閱讀 32,858評(píng)論 1 269
  • 我被黑心中介騙來泰國(guó)打工决摧, 沒想到剛下飛機(jī)就差點(diǎn)兒被人妖公主榨干…… 1. 我叫王不留,地道東北人。 一個(gè)月前我還...
    沈念sama閱讀 47,891評(píng)論 2 370
  • 正文 我出身青樓掌桩,卻偏偏與公主長(zhǎng)得像边锁,于是被迫代替她去往敵國(guó)和親。 傳聞我的和親對(duì)象是個(gè)殘疾皇子波岛,可洞房花燭夜當(dāng)晚...
    茶點(diǎn)故事閱讀 44,713評(píng)論 2 354

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