說起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í)行迭代器骨望。
最后歡迎大家吐槽,謝謝欣舵。