摘要:原文博客地址,歡迎學(xué)習(xí)交流點(diǎn)擊預(yù)覽讀了下的源碼,寫的相當(dāng)?shù)木?jiǎn),遇到處理中間件執(zhí)行的模塊決定學(xué)習(xí)一下這個(gè)模塊的源碼。當(dāng)在下游沒(méi)有更多的中間件執(zhí)行后,堆棧將展開并且每個(gè)中間件恢復(fù)執(zhí)行其上游行為。
原文博客地址,歡迎學(xué)習(xí)交流:點(diǎn)擊預(yù)覽
讀了下Koa的源碼,寫的相當(dāng)?shù)木?jiǎn),遇到處理中間件執(zhí)行的模塊koa-Compose,決定學(xué)習(xí)一下這個(gè)模塊的源碼。
閱讀本文可以學(xué)到:
Koa中間件的加載
next參數(shù)的來(lái)源
中間件控制權(quán)執(zhí)行順序
先上一段使用Koa啟動(dòng)服務(wù)的代碼:
放在文件app.js中
const koa = require("koa"); // require引入koa模塊 const app = new koa(); // 創(chuàng)建對(duì)象 app.use(async (ctx,next) => { console.log("第一個(gè)中間件") next(); }) app.use(async (ctx,next) => { console.log("第二個(gè)中間件") next(); }) app.use((ctx,next) => { console.log("第三個(gè)中間件") next(); }) app.use(ctx => { console.log("準(zhǔn)備響應(yīng)"); ctx.body = "hello" }) app.listen(3000)
以上代碼,可以使用node app.js啟動(dòng),啟動(dòng)后可以在瀏覽器中訪問(wèn)http://localhost:3000/
訪問(wèn)后,會(huì)在啟動(dòng)的命令窗口中打印出如下值:
第一個(gè)中間件
第二個(gè)中間件
第三個(gè)中間件
準(zhǔn)備響應(yīng)
代碼說(shuō)明:
app.use()方法,用來(lái)將中間件添加到隊(duì)列中
中間件就是傳給app.use()作為的參數(shù)的函數(shù)
使用app.use()將函數(shù)添加至隊(duì)列之中后,當(dāng)有請(qǐng)求時(shí),會(huì)依次觸發(fā)隊(duì)列中的函數(shù),也就是依次執(zhí)行一個(gè)個(gè)中間件函數(shù),執(zhí)行順序按照調(diào)用app.use()添加的順序。
在每個(gè)中間件函數(shù)中,會(huì)執(zhí)行next()函數(shù),意思是把控制權(quán)交到下一個(gè)中間件(實(shí)際上是調(diào)用next函數(shù)后,會(huì)調(diào)用下一個(gè)中間件函數(shù),后面解析源碼會(huì)有說(shuō)明),如果不調(diào)用next()函數(shù),不能調(diào)用下一個(gè)中間件函數(shù),那么隊(duì)列執(zhí)行也就終止了,在上面的代碼中表現(xiàn)就是不能響應(yīng)客戶端的請(qǐng)求了。
app.use(async (ctx,next) => { console.log("第二個(gè)中間件") // next(); 注釋之后,下一個(gè)中間件函數(shù)就不會(huì)執(zhí)行 })內(nèi)部過(guò)程分析
內(nèi)部利用app.use()添加到一個(gè)數(shù)組隊(duì)列中:
// app.use()函數(shù)內(nèi)部添加 this.middleware.push(fn); // 最終this.middleware為: this.middleware = [fn,fn,fn...]
具體參考這里Koa的源碼use函數(shù):https://github.com/koajs/koa/blob/master/lib/application.js#L104
使用koa-compose模塊的compose方法,把這個(gè)中間件數(shù)組合并成一個(gè)大的中間件函數(shù)
const fn = compose(this.middleware);
具體參考這里Koa的源碼https://github.com/koajs/koa/blob/master/lib/application.js#L126
在有請(qǐng)求后后會(huì)執(zhí)行這個(gè)中間件函數(shù)fn,進(jìn)而會(huì)把所有的中間件函數(shù)依次執(zhí)行
這樣片面的描述可能會(huì)不知所云,可以跳過(guò)不看,只是讓諸位知道Koa執(zhí)行中間件的過(guò)程
本篇主要是分析koa-compose的源碼,之后分析整個(gè)Koa的源碼后會(huì)做詳細(xì)說(shuō)明
所以最主要的還是使用koa-compose模塊來(lái)控制中間件的執(zhí)行,那么來(lái)一探究竟這個(gè)模塊如何進(jìn)行工作的
koa-composekoa-compose模塊可以將多個(gè)中間件函數(shù)合并成一個(gè)大的中間件函數(shù),然后調(diào)用這個(gè)中間件函數(shù)就可以依次執(zhí)行添加的中間件函數(shù),執(zhí)行一系列的任務(wù)。
源碼地址:https://github.com/koajs/compose/blob/master/index.js
先從一段代碼開始,創(chuàng)建一個(gè)compose.js的文件,寫入如下代碼:
const compose = require("koa-compose"); function one(ctx,next){ console.log("第一個(gè)"); next(); // 控制權(quán)交到下一個(gè)中間件(實(shí)際上是可以執(zhí)行下一個(gè)函數(shù)), } function two(ctx,next){ console.log("第二個(gè)"); next(); } function three(ctx,next){ console.log("第三個(gè)"); next(); } // 傳入中間件函數(shù)組成的數(shù)組隊(duì)列,合并成一個(gè)中間件函數(shù) const middlewares = compose([one, two, three]); // 執(zhí)行中間件函數(shù),函數(shù)執(zhí)行后返回的是Promise對(duì)象 middlewares().then(function (){ console.log("隊(duì)列執(zhí)行完畢"); })
可以使用node compose.js運(yùn)行此文件,命令行窗口打印出:
第一個(gè)
第二個(gè)
第三個(gè)
隊(duì)列執(zhí)行完畢
中間件這兒的重點(diǎn),是compose函數(shù)。compose函數(shù)的源代碼雖然很簡(jiǎn)潔,但要理解明白著實(shí)要下一番功夫。
以下為源碼分析:
"use strict" /** * Expose compositor. */ // 暴露compose函數(shù) module.exports = compose /** * Compose `middleware` returning * a fully valid middleware comprised * of all those which are passed. * * @param {Array} middleware * @return {Function} * @api public */ // compose函數(shù)需要傳入一個(gè)數(shù)組隊(duì)列 [fn,fn,fn,fn] function compose (middleware) { // 如果傳入的不是數(shù)組,則拋出錯(cuò)誤 if (!Array.isArray(middleware)) throw new TypeError("Middleware stack must be an array!") // 數(shù)組隊(duì)列中有一項(xiàng)不為函數(shù),則拋出錯(cuò)誤 for (const fn of middleware) { if (typeof fn !== "function") throw new TypeError("Middleware must be composed of functions!") } /** * @param {Object} context * @return {Promise} * @api public */ // compose函數(shù)調(diào)用后,返回的是以下這個(gè)匿名函數(shù) // 匿名函數(shù)接收兩個(gè)參數(shù),第一個(gè)隨便傳入,根據(jù)使用場(chǎng)景決定 // 第一次調(diào)用時(shí)候第二個(gè)參數(shù)next實(shí)際上是一個(gè)undefined,因?yàn)槌醮握{(diào)用并不需要傳入next參數(shù) // 這個(gè)匿名函數(shù)返回一個(gè)promise return function (context, next) { // last called middleware # //初始下標(biāo)為-1 let index = -1 return dispatch(0) function dispatch (i) { // 如果傳入i為負(fù)數(shù)且<=-1 返回一個(gè)Promise.reject攜帶著錯(cuò)誤信息 // 所以執(zhí)行兩次next會(huì)報(bào)出這個(gè)錯(cuò)誤。將狀態(tài)rejected,就是確保在一個(gè)中間件中next只調(diào)用一次 if (i <= index) return Promise.reject(new Error("next() called multiple times")) // 執(zhí)行一遍next之后,這個(gè)index值將改變 index = i // 根據(jù)下標(biāo)取出一個(gè)中間件函數(shù) let fn = middleware[i] // next在這個(gè)內(nèi)部中是一個(gè)局部變量,值為undefined // 當(dāng)i已經(jīng)是數(shù)組的length了,說(shuō)明中間件函數(shù)都執(zhí)行結(jié)束,執(zhí)行結(jié)束后把fn設(shè)置為undefined // 問(wèn)題:本來(lái)middleware[i]如果i為length的話取到的值已經(jīng)是undefined了,為什么要重新給fn設(shè)置為undefined呢? if (i === middleware.length) fn = next //如果中間件遍歷到最后了。那么。此時(shí)return Promise.resolve()返回一個(gè)成功狀態(tài)的promise // 方面之后做調(diào)用then if (!fn) return Promise.resolve() // try catch保證錯(cuò)誤在Promise的情況下能夠正常被捕獲。 // 調(diào)用后依然返回一個(gè)成功的狀態(tài)的Promise對(duì)象 // 用Promise包裹中間件,方便await調(diào)用 // 調(diào)用中間件函數(shù),傳入context(根據(jù)場(chǎng)景不同可以傳入不同的值,在KOa傳入的是ctx) // 第二個(gè)參數(shù)是一個(gè)next函數(shù),可在中間件函數(shù)中調(diào)用這個(gè)函數(shù) // 調(diào)用next函數(shù)后,遞歸調(diào)用dispatch函數(shù),目的是執(zhí)行下一個(gè)中間件函數(shù) // next函數(shù)在中間件函數(shù)調(diào)用后返回的是一個(gè)promise對(duì)象 // 讀到這里不得不佩服作者的高明之處。 try { return Promise.resolve(fn(context, function next () { return dispatch(i + 1) })) } catch (err) { return Promise.reject(err) } } } }
補(bǔ)充說(shuō)明:
根據(jù)以上的源碼分析得到,在一個(gè)中間件函數(shù)中不能調(diào)用兩次next(),否則會(huì)拋出錯(cuò)誤
function one(ctx,next){ console.log("第一個(gè)"); next(); next(); }
拋出錯(cuò)誤:
next() called multiple times
next()調(diào)用后返回的是一個(gè)Promise對(duì)象,可以調(diào)用then函數(shù)
function two(ctx,next){ console.log("第二個(gè)"); next().then(function(){ console.log("第二個(gè)調(diào)用then后") }); }
中間件函數(shù)可以是async/await函數(shù),在函數(shù)內(nèi)部可以寫任意的異步處理,處理得到結(jié)果后再進(jìn)行下一個(gè)中間件函數(shù)。
創(chuàng)建一個(gè)文件問(wèn)test-async.js,寫入以下代碼:
const compose = require("koa-compose"); // 獲取數(shù)據(jù) const getData = () => new Promise((resolve, reject) => { setTimeout(() => resolve("得到數(shù)據(jù)"), 2000); }); async function one(ctx,next){ console.log("第一個(gè),等待兩秒后再進(jìn)行下一個(gè)中間件"); // 模擬異步讀取數(shù)據(jù)庫(kù)數(shù)據(jù) await getData() // 等到獲取數(shù)據(jù)后繼續(xù)執(zhí)行下一個(gè)中間件 next() } function two(ctx,next){ console.log("第二個(gè)"); next() } function three(ctx,next){ console.log("第三個(gè)"); next(); } const middlewares = compose([one, two, three]); middlewares().then(function (){ console.log("隊(duì)列執(zhí)行完畢"); })
可以使用node test-async.js運(yùn)行此文件,命令行窗口打印出:
第一個(gè),等待兩秒后再進(jìn)行下一個(gè)中間件
第二個(gè)
第三個(gè)
第二個(gè)調(diào)用then后
隊(duì)列執(zhí)行完畢
在以上打印輸出過(guò)程中,執(zhí)行第一個(gè)中間件后,在內(nèi)部會(huì)有一個(gè)異步操作,使用了async/await后得到同步操作一樣的體驗(yàn),這步操作可能是讀取數(shù)據(jù)庫(kù)數(shù)據(jù)或者讀取文件,讀取數(shù)據(jù)后,調(diào)用next()執(zhí)行下一個(gè)中間件。這里模擬式等待2秒后再執(zhí)行下一個(gè)中間件。
更多參考了async/await:https://developer.mozilla.org/zh-CN/docs/Web/JavaScript/Reference/Statements/async_function執(zhí)行順序
https://developer.mozilla.org/zh-CN/docs/Web/JavaScript/Reference/Operators/await
調(diào)用next后,執(zhí)行的順序會(huì)讓人產(chǎn)生迷惑,創(chuàng)建文件為text-next.js,寫入以下代碼:
const koa = require("koa"); const app = new koa(); app.use((ctx, next) => { console.log("第一個(gè)中間件函數(shù)") next(); console.log("第一個(gè)中間件函數(shù)next之后"); }) app.use(async (ctx, next) => { console.log("第二個(gè)中間件函數(shù)") next(); console.log("第二個(gè)中間件函數(shù)next之后"); }) app.use(ctx => { console.log("響應(yīng)"); ctx.body = "hello" }) app.listen(3000)
以上代碼,可以使用node text-next.js啟動(dòng),啟動(dòng)后可以在瀏覽器中訪問(wèn)http://localhost:3000/
訪問(wèn)后,會(huì)在啟動(dòng)的命令窗口中打印出如下值:
第一個(gè)中間件函數(shù)
第二個(gè)中間件函數(shù)
響應(yīng)
第二個(gè)中間件函數(shù)next之后
第一個(gè)中間件函數(shù)next之后
是不是對(duì)這個(gè)順序產(chǎn)生了深深地疑問(wèn),為什么會(huì)這樣呢?
當(dāng)一個(gè)中間件調(diào)用 next() 則該函數(shù)暫停并將控制傳遞給定義的下一個(gè)中間件。當(dāng)在下游沒(méi)有更多的中間件執(zhí)行后,堆棧將展開并且每個(gè)中間件恢復(fù)執(zhí)行其上游行為。
過(guò)程是這樣的:
先執(zhí)行第一個(gè)中間件函數(shù),打印出 "第一個(gè)中間件函數(shù)"
調(diào)用了next,不再繼續(xù)向下執(zhí)行
執(zhí)行第二個(gè)中間件函數(shù),打印出 "第二個(gè)中間件函數(shù)"
調(diào)用了next,不再繼續(xù)向下執(zhí)行
執(zhí)行最后一個(gè)中間件函數(shù),打印出 "響應(yīng)"
...
最后一個(gè)中間函數(shù)執(zhí)行后,上一個(gè)中間件函數(shù)收回控制權(quán),繼續(xù)執(zhí)行,打印出 "第二個(gè)中間件函數(shù)next之后"
第二個(gè)中間件函數(shù)執(zhí)行后,上一個(gè)中間件函數(shù)收回控制權(quán),繼續(xù)執(zhí)行,打印出 "第一個(gè)中間件函數(shù)next之后"
借用一張圖來(lái)直觀的說(shuō)明:
具體看別人怎么理解next的順序:https://segmentfault.com/q/1010000011033764
最近在看Koa的源碼,以上屬于個(gè)人理解,如有偏差歡迎指正學(xué)習(xí),謝謝。
參考資料:https://koa.bootcss.com/
https://cnodejs.org/topic/58fd8ec7523b9d0956dad945
文章版權(quán)歸作者所有,未經(jīng)允許請(qǐng)勿轉(zhuǎn)載,若此文章存在違規(guī)行為,您可以聯(lián)系管理員刪除。
轉(zhuǎn)載請(qǐng)注明本文地址:http://systransis.cn/yun/93024.html
摘要:接上次挖的坑,對(duì)相關(guān)的源碼進(jìn)行分析第一篇。和同為一批人進(jìn)行開發(fā),與相比,顯得非常的迷你。在接收到一個(gè)請(qǐng)求后,會(huì)拿之前提到的與來(lái)創(chuàng)建本次請(qǐng)求所使用的上下文。以及如果沒(méi)有手動(dòng)指定,會(huì)默認(rèn)指定為。 接上次挖的坑,對(duì)koa2.x相關(guān)的源碼進(jìn)行分析 第一篇。 不得不說(shuō),koa是一個(gè)很輕量、很優(yōu)雅的http框架,尤其是在2.x以后移除了co的引入,使其代碼變得更為清晰。 express和ko...
摘要:從一個(gè)對(duì)象里面提取需要的屬性這篇文章一直想寫了還想起那一夜我看到白天的代碼,實(shí)在太美了。 koa源碼lib主要文件有 application.js context.js request.js response.js application.js koa主要的邏輯處理代碼整個(gè)koa的處理 context.js 將req,res方法 掛載在這,生成ctx上下文對(duì)象 requests....
摘要:引言最近空閑時(shí)間讀了一下的源碼在閱讀的源碼的過(guò)程中,我的感受是代碼簡(jiǎn)潔思路清晰不得不佩服大神的水平。調(diào)用的時(shí)候就跟有區(qū)別使用必須使用來(lái)調(diào)用除了上面的的構(gòu)造函數(shù)外,還暴露了一些公用的,比如兩個(gè)常見(jiàn)的,一個(gè)是,一個(gè)是。 引言 最近空閑時(shí)間讀了一下Koa2的源碼;在閱讀Koa2(version 2.2.0)的源碼的過(guò)程中,我的感受是代碼簡(jiǎn)潔、思路清晰(不得不佩服大神的水平)。下面是我讀完之后...
摘要:于是抱著知其然也要知其所以然的想法,開始閱讀的源代碼。問(wèn)題讀源代碼時(shí),自然是帶著諸多問(wèn)題的。源代碼如下在被處理完后,每當(dāng)有新請(qǐng)求,便會(huì)調(diào)用,去處理請(qǐng)求。接下來(lái)會(huì)繼續(xù)寫一些閱讀筆記,因?yàn)榭吹脑创a確實(shí)是獲益匪淺。 本筆記共四篇Koa源碼閱讀筆記(1) -- coKoa源碼閱讀筆記(2) -- composeKoa源碼閱讀筆記(3) -- 服務(wù)器の啟動(dòng)與請(qǐng)求處理Koa源碼閱讀筆記(4) -...
摘要:到此為止,我們就基本講清楚了中的中間件洋蔥模型是如何自動(dòng)執(zhí)行的。 koa被認(rèn)為是第二代web后端開發(fā)框架,相比于前代express而言,其最大的特色無(wú)疑就是解決了回調(diào)金字塔的問(wèn)題,讓異步的寫法更加的簡(jiǎn)潔。在使用koa的過(guò)程中,其實(shí)一直比較好奇koa內(nèi)部的實(shí)現(xiàn)機(jī)理。最近終于有空,比較深入的研究了一下koa一些原理,在這里會(huì)寫一系列文章來(lái)記錄一下我的學(xué)習(xí)心得和理解。 在我看來(lái),koa最核心...
閱讀 3804·2021-09-23 11:32
閱讀 2470·2021-09-06 15:01
閱讀 1630·2021-08-18 10:24
閱讀 3468·2019-12-27 11:44
閱讀 3615·2019-08-30 15:52
閱讀 2522·2019-08-30 11:11
閱讀 696·2019-08-29 17:27
閱讀 608·2019-08-29 16:22