前言
最近幾天花了比較長(zhǎng)的時(shí)間在koa(1)的源碼分析上面顿颅,初次看的時(shí)候缸濒,?被中間件執(zhí)行那段整的暈乎乎的,完全不知道所以粱腻,再次看庇配,好像明白了些什么,再反復(fù)看绍些,我去捞慌,?簡(jiǎn)直神了,簡(jiǎn)直淚流滿面柬批,簡(jiǎn)直喪心病狂靶ピ琛!B芸臁锻霎!
用在前面
下面的例子會(huì)在控制臺(tái)中打印出一些信息(具體打印出什么?可以猜猜??)揪漩,然后返回
hello world
旋恼。
let koa = require('koa')
let app = koa()
app.use(function * (next) {
console.log('generate1----start')
yield next
console.log('generate1----end')
})
app.use(function * (next) {
console.log('generate2----start')
yield next
console.log('generate2----end')
this.body = 'hello world'
})
app.listen(3000)
用過(guò)koa的同學(xué)都知道添加中間件的方式是使用koa實(shí)例的use
方法,并傳入一個(gè)generator函數(shù)奄容,這個(gè)generator函數(shù)可以接受一個(gè)next
(這個(gè)next到底是啥冰更?這里先不闡明,在后面會(huì)仔細(xì)說(shuō)明)昂勒。
執(zhí)行use干了嘛
這是koa的構(gòu)造函數(shù)蜀细,為了沒(méi)有其他信息的干擾,我去除了一些暫時(shí)用不到的代碼戈盈,這里我們把目光聚焦?在middleware
這個(gè)數(shù)組即可奠衔。
function Application() {
// xxx
this.middleware = []; // 這個(gè)數(shù)組就是用來(lái)裝一個(gè)個(gè)中間件的
// xxx
}
?接下來(lái)我們要看use方法了
同樣去除了一些暫時(shí)不用的代碼,可以看到每次執(zhí)行use方法塘娶,就把外面?zhèn)鬟M(jìn)來(lái)的generator函數(shù)push到middleware數(shù)組中
app.use = function(fn){
// xxx
this.middleware.push(fn);
// xxx
};
好啦归斤!你已經(jīng)知道koa中是預(yù)先通過(guò)use方法,將請(qǐng)求可能會(huì)經(jīng)過(guò)的中間件裝在了一個(gè)數(shù)組中刁岸。
接下來(lái)我們要開(kāi)始本文的重點(diǎn)了脏里,當(dāng)一個(gè)請(qǐng)求?到來(lái)的時(shí)候,是怎樣經(jīng)過(guò)中間件虹曙,怎么跑起來(lái)的
首先我們只要知道下面這段callback
函數(shù)就是請(qǐng)求到來(lái)的時(shí)候執(zhí)行的回調(diào)即可(同樣盡量去除了我們不用的代碼)
app.callback = function(){
// xxx
var fn = this.experimental
? compose_es7(this.middleware)
: co.wrap(compose(this.middleware));
// xxx
return function(req, res){
// xxx
fn.call(ctx).then(function () {
respond.call(ctx);
}).catch(ctx.onerror);
// xxx
}
};
這段代碼可以分成兩個(gè)部分
- 請(qǐng)求前的中間件初始化處理部分
- 請(qǐng)求到來(lái)時(shí)的中間件運(yùn)行部分
我們分部分來(lái)說(shuō)一下
var fn = this.experimental
? compose_es7(this.middleware)
: co.wrap(compose(this.middleware));
這段代碼對(duì)experimental
做了下判斷迫横,如果設(shè)置為了true
那么koa中將可以支持傳入async函數(shù)番舆,否則就執(zhí)行co.wrap(compose(this.middleware))
。?
只有一行初始化中間件就做完啦矾踱?
我知道koa很屌恨狈,但也別這么屌好不好,所以說(shuō)評(píng)價(jià)一個(gè)好的程序員不是由代碼量決定的
我們來(lái)看下這段代碼到底有什么神奇的地方
compose(this.middleware)
把裝著中間件middleware
的數(shù)組作為參數(shù)傳進(jìn)了compose
這個(gè)方法介返,那么compose做了什么事呢拴事?其實(shí)就是把原本毫無(wú)關(guān)系的一個(gè)個(gè)中間件給首尾串起來(lái)了沃斤,于是他們之間?就有了千絲萬(wàn)縷的聯(lián)系圣蝎。
function compose(middleware){
return function *(next){
// 第一次得到next是由于*noop生成的generator對(duì)象
if (!next) next = noop();
var i = middleware.length;
// 從后往前開(kāi)始執(zhí)行middleware中的generator函數(shù)
while (i--) {
// 把后一個(gè)?中間件得到的generator?對(duì)象傳給前一個(gè)作為第一個(gè)參數(shù)存在
next = middleware[i].call(this, next);
}
return yield *next;
}
}
function *noop(){}
文字解釋一下就是,compose將中間件從最后一個(gè)開(kāi)始處理衡瓶,并一直往前?直到第一個(gè)中間件徘公。其中非常關(guān)鍵的就是將后一個(gè)中間件得到generator對(duì)象作為參數(shù)(這個(gè)參數(shù)就是文章開(kāi)頭說(shuō)到的next啦,也就是說(shuō)next其實(shí)是一個(gè)?generator對(duì)象
)傳給前一個(gè)中間件哮针。當(dāng)然最后一個(gè)中間件的參數(shù)next
是一個(gè)空的generator函數(shù)?生成的對(duì)象关面。
我們自己來(lái)寫(xiě)一個(gè)?簡(jiǎn)單的例子說(shuō)明compose是如何將多個(gè)generator函數(shù)串聯(lián)起來(lái)的
function * gen1 (next) {
yield 'gen1'
yield * next // 開(kāi)始執(zhí)行下一個(gè)中間件
yield 'gen1-end' // 下一個(gè)中間件執(zhí)行完成再繼續(xù)執(zhí)行g(shù)en1中間件的邏輯
}
function * gen2 (next) {
yield 'gen2'
yield * next // 開(kāi)始執(zhí)行下一個(gè)中間件
yield 'gen2-end' // 下一個(gè)中間件執(zhí)行完成再繼續(xù)執(zhí)行g(shù)en2中間件的邏輯
}
function * gen3 (next) {
yield 'gen3'
yield * next // 開(kāi)始執(zhí)行下一個(gè)中間件
yield 'gen3-end' // 下一個(gè)中間件執(zhí)行完成再繼續(xù)執(zhí)行g(shù)en3中間件的邏輯
}
function * noop () {}
var middleware = [gen1, gen2, gen3]
var len = middleware.length
var next = noop() // 提供給最后一個(gè)中間件的參數(shù)
while(len--) {
next = middleware[len].call(null, next)
}
function * letGo (next) {
yield * next
}
var g = letGo(next)
g.next() // {value: "gen1", done: false}
g.next() // {value: "gen2", done: false}
g.next() // {value: "gen3", done: false}
g.next() // {value: "gen3-end", done: false}
g.next() // {value: "gen2-end", done: false}
g.next() // {value: "gen1-end", done: false}
g.next() // {value: undefined, done: true}
看到了嗎?中間件?被串起來(lái)之后執(zhí)行的順序是
gen1 -> gen2 -> gen3 -> noop -> gen3 -> gen2 -> gen1
從而首尾相連十厢,進(jìn)而發(fā)生了關(guān)系??等太。
co.wrap
通過(guò)compose處理后返回了一個(gè)generator函數(shù)。
co.wrap(compose(this.middleware))
所有上述代碼可以理解為
co.wrap(function * gen ())
好蛮放,我們?cè)倏纯?code>co.wrap做了什么,慢慢地一步步靠近了哦
co.wrap = function (fn) {
createPromise.__generatorFunction__ = fn;
return createPromise;
function createPromise() {
return co.call(this, fn.apply(this, arguments));
}
}
可以看到?co.wrap
返回了一個(gè)普通函數(shù)createPromise
,這個(gè)函數(shù)就是文章開(kāi)頭的fn
啦缩抡。
var fn = this.experimental
? compose_es7(this.middleware)
: co.wrap(compose(this.middleware));
中間件開(kāi)始跑起來(lái)啦
前面已經(jīng)說(shuō)完了,中間件是如何初始化的包颁,即如果由不相干到關(guān)系密切了瞻想,接下來(lái)開(kāi)始說(shuō)請(qǐng)求到來(lái)時(shí),初始化好的中間件?是怎么跑的娩嚼。
fn.call(ctx).then(function () {
respond.call(ctx);
}).catch(ctx.onerror);
這一段便是請(qǐng)求到來(lái)手即將要經(jīng)過(guò)的中間件執(zhí)行部分蘑险,fn執(zhí)行之后返回的是一個(gè)Promise,koa通過(guò)注冊(cè)成功和失敗的回調(diào)函數(shù)來(lái)分別處理請(qǐng)求岳悟。
讓我們回到
co.wrap = function (fn) {
// xxx
function createPromise() {
return co.call(this, fn.apply(this, arguments));
}
}
createPromise
里面的fn就是經(jīng)過(guò)compose處理中間件后返回的一個(gè)generator函數(shù)佃迄,那么執(zhí)行之后拿到的就是一個(gè)generator對(duì)象了,并把這個(gè)對(duì)象傳經(jīng)經(jīng)典的co里面啦贵少。如果你需要對(duì)co的源碼了解歡迎查看昨天寫(xiě)的走一步再走一步呵俏,揭開(kāi)co的神秘面紗,好了春瞬,接下來(lái)就是看co里面如何處理這個(gè)被compose處理過(guò)的generator對(duì)象了
再回顧一下co
function co(gen) {
var ctx = this;
var args = slice.call(arguments, 1)
// we wrap everything in a promise to avoid promise chaining,
// which leads to memory leak errors.
// see https://github.com/tj/co/issues/180
return new Promise(function(resolve, reject) {
if (typeof gen === 'function') gen = gen.apply(ctx, args);
if (!gen || typeof gen.next !== 'function') return resolve(gen);
onFulfilled();
/**
* @param {Mixed} res
* @return {Promise}
* @api private
*/
function onFulfilled(res) {
var ret;
try {
ret = gen.next(res);
} catch (e) {
return reject(e);
}
next(ret);
}
/**
* @param {Error} err
* @return {Promise}
* @api private
*/
function onRejected(err) {
var ret;
try {
ret = gen.throw(err);
} catch (e) {
return reject(e);
}
next(ret);
}
/**
* Get the next value in the generator,
* return a promise.
*
* @param {Object} ret
* @return {Promise}
* @api private
*/
function next(ret) {
if (ret.done) return resolve(ret.value);
var value = toPromise.call(ctx, ret.value);
if (value && isPromise(value)) return value.then(onFulfilled, onRejected);
return onRejected(new TypeError('You may only yield a function, promise, generator, array, or object, '
+ 'but the following object was passed: "' + String(ret.value) + '"'));
}
});
}
我們直接看一下onFulfilled
,這個(gè)時(shí)候第一次進(jìn)co的時(shí)候因?yàn)橐呀?jīng)是generator對(duì)象所以會(huì)直接執(zhí)行onFulfilled()
function onFulfilled(res) {
var ret;
try {
ret = gen.next(res);
} catch (e) {
return reject(e);
}
next(ret);
}
而gen.next
正是用于去執(zhí)行中間件的業(yè)務(wù)邏輯柴信,當(dāng)遇到y(tǒng)ield語(yǔ)句的時(shí)候,將?緊隨其后的結(jié)果返回賦值給ret
,通常這里的ret宽气,就是我們文中說(shuō)道的next
,也就是當(dāng)前中間件的下一個(gè)中間件随常。
拿到下一個(gè)中間件后把他交給next
去處理
function next(ret) {
if (ret.done) return resolve(ret.value);
var value = toPromise.call(ctx, ret.value);
if (value && isPromise(value)) return value.then(onFulfilled, onRejected);
return onRejected(new TypeError('You may only yield a function, promise, generator, array, or object, '
+ 'but the following object was passed: "' + String(ret.value) + '"'));
}
當(dāng)中間件執(zhí)行結(jié)束了潜沦,就把Promise的狀態(tài)設(shè)置為成功。否則就將ret
(也就是下一個(gè)中間件)再用co包一次绪氛。主要看toPromise
的這幾行代碼即可
function toPromise(obj) {
// xxx
if (isGeneratorFunction(obj) || isGenerator(obj)) return co.call(this, obj);
// xxx
}
注意噢toPromise
這個(gè)時(shí)候的返回值是一個(gè)Promise唆鸡,這個(gè)非常關(guān)鍵,是下一個(gè)中間件執(zhí)行完成之后回溯到上一個(gè)中間件中斷執(zhí)行處繼續(xù)執(zhí)行的關(guān)鍵
function next(ret) {
// xxx
var value = toPromise.call(ctx, ret.value);
// 即通過(guò)前面toPromise返回的Promise實(shí)現(xiàn)枣察,當(dāng)后一個(gè)中間件執(zhí)行結(jié)束争占,回退到上一個(gè)中間件中斷處繼續(xù)執(zhí)行
if (value && isPromise(value)) return value.then(onFulfilled, onRejected);
// xxx
}
看到這里,我們可以總結(jié)出序目,幾乎koa的中間件都會(huì)被co給包裝一次臂痕,而?每一個(gè)中間件又可以通過(guò)Promise的then去監(jiān)測(cè)其后一個(gè)?中間件是否結(jié)束,后一個(gè)中間件結(jié)束后?會(huì)執(zhí)行前一個(gè)中間件用then監(jiān)聽(tīng)的操作猿涨,這個(gè)操作便是執(zhí)行該中間件yield next后面的那些代碼
打個(gè)比方:
當(dāng)koa中接收到一個(gè)請(qǐng)求的時(shí)候握童,?請(qǐng)求將經(jīng)過(guò)兩個(gè)中間件,分別是中間件1
和中間件2
叛赚,
中間件1
// 中間件1在yield 中間件2之前的代碼
yield 中間件2
// 中間件2執(zhí)行完成之后繼續(xù)執(zhí)行中間件1的代碼
中間件2
// 中間件2在yield noop中間件之前的代碼
yield noop中間件
// noop中間件執(zhí)行完成之后繼續(xù)執(zhí)行中間件2的代碼
那么處理的過(guò)程就是co會(huì)立即調(diào)用onFulfilled來(lái)執(zhí)行中間件1前半部分代碼澡绩,遇到yield 中間件2
的時(shí)候得到中間件2generator對(duì)象,緊接著俺附,又把這個(gè)對(duì)象放到co里面繼續(xù)執(zhí)行一遍肥卡,以此類推下去知道最后一個(gè)中間件(我們這里的?指的是那個(gè)空的noop中間件)執(zhí)行結(jié)束,繼而馬上調(diào)用promise的resolve方法表示結(jié)束事镣,ok步鉴,這個(gè)時(shí)候中間件2監(jiān)聽(tīng)到noop執(zhí)行結(jié)束了,馬上又去執(zhí)行了onFulfilled來(lái)執(zhí)行yield noop中間件后半部分代碼蛮浑,好啦這個(gè)?時(shí)候中間件2也執(zhí)行結(jié)束了唠叛,也會(huì)馬上調(diào)用promise的resolve方法表示結(jié)束,??ok,這個(gè)時(shí)候中間件1監(jiān)聽(tīng)到中間件2執(zhí)行結(jié)束了沮稚,馬上又去執(zhí)行了onFulfilled來(lái)執(zhí)行yield 中間件2后半部分代碼艺沼,最后中間件全部執(zhí)行完了,就執(zhí)行respond.call(ctx);
啊 啊 啊好繞蕴掏,不過(guò)慢慢看障般,仔細(xì)想,還是可以想明白的盛杰。用代碼表示這個(gè)過(guò)程有點(diǎn)類似
new Promise((resolve, reject) => {
// 我是中間件1
yield new Promise((resolve, reject) => {
// 我是中間件2
yield new Promise((resolve, reject) => {
// 我是body
})
// 我是中間件2
})
// 我是中間件1
});
結(jié)尾
羅里吧嗦說(shuō)了一大堆挽荡,也不知道有沒(méi)有把執(zhí)行原理說(shuō)明白。
如果對(duì)你理解koa有些許幫助即供,不介意的話定拟,點(diǎn)擊源碼地址點(diǎn)顆小星星吧
如果對(duì)你理解koa有些許幫助,不介意的話逗嫡,點(diǎn)擊源碼地址點(diǎn)顆小星星吧
如果對(duì)你理解koa有些許幫助青自,不介意的話株依,點(diǎn)擊源碼地址點(diǎn)顆小星星吧