koa中間件核心(koa-compose)源碼解讀分析
最近經(jīng)常使用koa進(jìn)行服務(wù)端開(kāi)發(fā),迷戀上了koa的洋蔥模型,覺(jué)得這玩意太好用了。而且koa是以精簡(jiǎn)為主,沒(méi)有很多集成東西,所有的東西都需按需加載,這個(gè)更是太合我胃口了哈哈哈哈。
相對(duì)與express的中間件,express的中間件使用的是串聯(lián),就像冰糖葫蘆一樣一個(gè)接著一個(gè),而koa使用的V型結(jié)構(gòu)(洋蔥模型),這將給我們的中間件提供更加靈活的處理方式。
基于對(duì)洋蔥模型的熱衷,所以對(duì)koa的洋蔥模型進(jìn)行一探究竟,不管是koa1還是koa2的中間件都是基于koa-compose進(jìn)行編寫(xiě)的,這種V型結(jié)構(gòu)的實(shí)現(xiàn)就來(lái)源于koa-compose。
附上源碼先:
function compose (middleware) { // 參數(shù)middleware 是一個(gè)中間件數(shù)組,存放我們用app.use()一個(gè)個(gè)串聯(lián)起來(lái)的中間件 // 判斷中間件列表是否為數(shù)組,如果不為數(shù)組,則拋出類(lèi)型錯(cuò)誤 if (!Array.isArray(middleware)) throw new TypeError('Middleware stack must be an array!') // 判斷中間件是否為函數(shù),如果不為函數(shù),則拋出類(lèi)型錯(cuò)誤 for (const fn of middleware) { if (typeof fn !== 'function') throw new TypeError('Middleware must be composed of functions!') } /** 1. @param {Object} context 2. @return {Promise} 3. @api public */ return function (context, next) { // 這里next指的是洋蔥模型的中心函數(shù) // context是一個(gè)配置對(duì)象,保存著一些配置,當(dāng)然也可以利用context將一些參數(shù)往下一個(gè)中間傳遞 // last called middleware # let index = -1 // index是記錄執(zhí)行的中間件的索引 return dispatch(0) // 執(zhí)行第一個(gè)中間件 然后通過(guò)第一個(gè)中間件遞歸調(diào)用下一個(gè)中間件 function dispatch (i) { // 這里是保證同個(gè)中間件中一個(gè)next()不被調(diào)用多次調(diào)用 // 當(dāng)next()函數(shù)被調(diào)用兩次的時(shí)候,i會(huì)小于index,然后拋出錯(cuò)誤 if (i <= index) return Promise.reject(new Error('next() called multiple times')) index = i let fn = middleware[i] // 取出要執(zhí)行的中間件 if (i === middleware.length) fn = next // 如果i 等于 中間件的長(zhǎng)度,即到了洋蔥模型的中心(最后一個(gè)中間件) if (!fn) return Promise.resolve() // 如果中間件為空,即直接resolve try { // 遞歸執(zhí)行下一個(gè)中間件 (下面會(huì)重點(diǎn)分析這個(gè)) return Promise.resolve(fn(context, dispatch.bind(null, i + 1))); } catch (err) { return Promise.reject(err) } } } }
看到這里,如果下面的那些能夠理解,那么下面的可以不用看的,還是不能理解的就繼續(xù)往下看,詳細(xì)一點(diǎn)的分析。
首先,我們用app.use()添加一個(gè)中間件,在koa的源碼里app.use()這個(gè)方法就是將一個(gè)中間件push進(jìn)middleware這個(gè)中間件列表里。源碼里是這么寫(xiě)的(這個(gè)比較簡(jiǎn)單 不做分析):
use(fn) { if (typeof fn !== 'function') throw new TypeError('middleware must be a function!'); if (isGeneratorFunction(fn)) { deprecate('Support for generators will be removed in v3. ' + 'See the documentation for examples of how to convert old middleware ' + 'https://github.com/koajs/koa/blob/master/docs/migration.md'); fn = convert(fn); } debug('use %s', fn._name || fn.name || '-'); this.middleware.push(fn); return this; }
compose這個(gè)方法傳入一個(gè)中間件列表middleware,這個(gè)列表就是我們使用use()添加進(jìn)去的方法列表,首先會(huì)判斷列表是否為數(shù)組,中間件是否為方法,如果不是就直接拋出類(lèi)型錯(cuò)誤。
- compose返回的是一個(gè)函數(shù),這里使用閉包來(lái)緩存中間件列表,然后這個(gè)函數(shù)接收兩個(gè)參數(shù),第一個(gè)參數(shù)是context,是一個(gè)存放配置信息的對(duì)象。第二份參數(shù)是一個(gè)next方法,也是洋蔥模型的中心或者說(shuō)是V型模型的拐點(diǎn)。
- 創(chuàng)建一個(gè)index變量來(lái)保存執(zhí)行的中間件索引,然后從第一個(gè)中間件開(kāi)始開(kāi)始遞歸執(zhí)行。
let index = -1 return dispatch(0)
dispatch方法就是執(zhí)行中間件,先判斷索引,如果i小于index那么說(shuō)明在同一個(gè)中間件里執(zhí)行了兩次或兩次以上的next函數(shù),如果i>index則說(shuō)明該中間件還未執(zhí)行,那么將該中間件的所以記錄下來(lái)
if (i <= index) return Promise.reject(new Error('next() called multiple times')) index = i
取出該中間件,如果i等于中間件的長(zhǎng)圖,則說(shuō)明執(zhí)行到了洋蔥模型的中心,則最后一個(gè)中間件,如果中間件為空,那么就直接resovle掉
let fn = middleware[i] if(i === middleware.length){ fn = next } if(!fn){ return Promise.resolve() }
到了最刺激的部分了,也是有點(diǎn)繞的部分了,首先為啥return的是一個(gè)Promise的對(duì)象(Promise.resolve也是一個(gè)promise對(duì)象)呢,因?yàn)槲覀僡wait next()的時(shí)候,await是等待且執(zhí)行一個(gè)async函數(shù)的完成,async會(huì)默認(rèn)返回一個(gè)promise對(duì)象,所以這里return的是一個(gè)promise對(duì)象。我們?cè)诿總€(gè)中間里面await mext() next()指的就是下一個(gè)中間件,也就是
fn(context, function next () { return dispatch(i + 1) })
所以我們上一個(gè)中的await 等待的就是dispatch(i+1)的執(zhí)行完成,dispatch返回的是Promise.resolve(fn(context, function next () { xxxx })),這樣來(lái)看雖然一開(kāi)始只執(zhí)行了dispatch(0),但是是由這個(gè)函數(shù)形成了一條執(zhí)行鏈。
以三個(gè)中間件執(zhí)行為例,dispatch(0)執(zhí)行后就形成:
Promise.resolve( // 第一個(gè)中間件 function(context,next){ // 這里的next第二個(gè)中間件也就是dispatch(1) // await next上的代碼 (中間件1) await Promise.resolve( // 第二個(gè)中間件 function(context,next){ // 這里的next第二個(gè)中間件也就是dispatch(2) // await next上的代碼 (中間件2) await Promise.resolve( // 第三個(gè)中間件 function(context,next){ // 這里的next第二個(gè)中間件也就是dispatch(3) // await next上的代碼 (中間件3) await Promise.resolve() // await next下的代碼 (中間件3) } ) // await next下的代碼 (中間件2) } ) // await next下的代碼 (中間件2) } )
先執(zhí)行await上面的代碼,然后等待最后一個(gè)中間件resolve一個(gè)個(gè)往上傳遞,這就形成了一個(gè)洋蔥模型。
最后附上測(cè)試代碼:
async function test1(ctx, next) { console.log('中間件1上'); await next(); console.log('中間件1下'); }; async function test2(ctx, next) { console.log('中間件2上'); await next(); console.log('中間件2下'); }; async function test3(ctx, next) { console.log('中間件3上'); await next(); console.log('中間件3下'); }; let middleware = [test1, test2, test3]; let cp = compose(middleware); cp('ctx', function() { console.log('中心'); });
OK,到這里koa2的中間件核心(koa-compose)就解析完成了,一開(kāi)始看的時(shí)候,也被繞了好久,多看幾遍多分析一步一步捋順。koa1的中間件等過(guò)幾天有時(shí)間再補(bǔ)上吧,koa1是基于generator,源碼比起koa2相對(duì)簡(jiǎn)單。
最近在看koa2源碼,等有時(shí)間再繼續(xù)更新koa一些源碼的分析。
到此這篇關(guān)于koa中間件核心(koa-compose)源碼解讀分析的文章就介紹到這了,更多相關(guān)koa中間件核心內(nèi)容請(qǐng)搜索腳本之家以前的文章或繼續(xù)瀏覽下面的相關(guān)文章希望大家以后多多支持腳本之家!
相關(guān)文章
Mongoose中document與object的區(qū)別示例詳解
這篇文章主要給大家介紹了關(guān)于Mongoose中document與object區(qū)別的相關(guān)資料,文中通過(guò)示例代碼介紹的非常詳細(xì),對(duì)大家的學(xué)習(xí)或者工作具有一定的參考學(xué)習(xí)價(jià)值,需要的朋友可以參考借鑒,下面隨著小編來(lái)一起學(xué)習(xí)學(xué)習(xí)吧。2017-09-09簡(jiǎn)單易懂的nvm和Node.js版本控制的實(shí)現(xiàn)
NVM是Node.js的版本管理工具,可以方便地在不同版本的Node.js之間切換,本文主要介紹了簡(jiǎn)單易懂的nvm和Node.js版本控制的實(shí)現(xiàn),具有一定的參考價(jià)值,感興趣的可以了解一下2023-10-10nvm版本導(dǎo)致npm?install報(bào)錯(cuò)Unexpected?token?'.'的解決辦法
最近做項(xiàng)目遇到npm install 的問(wèn)題,下面這篇文章主要給大家介紹了關(guān)于nvm版本導(dǎo)致npm?install報(bào)錯(cuò)Unexpected?token?'.'的解決辦法,文中通過(guò)圖文介紹的非常詳細(xì),需要的朋友可以參考下2022-07-07基于Node.js模板引擎教程-jade速學(xué)與實(shí)戰(zhàn)1
下面小編就為大家?guī)?lái)一篇基于Node.js模板引擎教程-jade速學(xué)與實(shí)戰(zhàn)1。小編覺(jué)得挺不錯(cuò)的,現(xiàn)在就分享給大家,也給大家做個(gè)參考。一起跟隨小編過(guò)來(lái)看看吧2017-09-09WebSocket Node構(gòu)建HTTP隧道實(shí)現(xiàn)實(shí)例
這篇文章主要為大家介紹了WebSocket Node構(gòu)建HTTP隧道實(shí)現(xiàn)實(shí)例詳解,有需要的朋友可以借鑒參考下,希望能夠有所幫助,祝大家多多進(jìn)步,早日升職加薪2023-11-11node.js部署之啟動(dòng)后臺(tái)運(yùn)行forever的方法
今天小編就為大家分享一篇node.js部署之啟動(dòng)后臺(tái)運(yùn)行forever的方法,具有很好的參考價(jià)值,希望對(duì)大家有所幫助。一起跟隨小編過(guò)來(lái)看看吧2018-05-05基于npm?install或run時(shí)一些報(bào)錯(cuò)的解決方案
這篇文章主要介紹了基于npm?install或run時(shí)一些報(bào)錯(cuò)的解決方案,具有很好的參考價(jià)值,希望對(duì)大家有所幫助。如有錯(cuò)誤或未考慮完全的地方,望不吝賜教2023-06-06