摘要:最近一年零零散散看了不少開源項(xiàng)目的源碼多少也有點(diǎn)心得這里想通過這篇文章總結(jié)一下這里以為例前段時(shí)間其實(shí)看過的源碼但是發(fā)現(xiàn)理解的有點(diǎn)偏差所以重新過一遍不得不說閱讀的代碼真的收獲很大沒啥奇技淫巧代碼優(yōu)雅設(shè)計(jì)極好注釋什么的就更不用說了總之還是推薦把
最近一年零零散散看了不少開源項(xiàng)目的源碼, 多少也有點(diǎn)心得, 這里想通過這篇文章總結(jié)一下, 這里以Koa為例, 前段時(shí)間其實(shí)看過Koa的源碼, 但是發(fā)現(xiàn)理解的有點(diǎn)偏差, 所以重新過一遍.
不得不說閱讀tj的代碼真的收獲很大, 沒啥奇技淫巧, 代碼優(yōu)雅, 設(shè)計(jì)極好. 注釋什么的就更不用說了. 總之還是推薦把他的項(xiàng)目都過一遍(逃)
跑通例子Koa作為一個(gè)web框架, 我們要去閱讀它的源碼肯定是得知道它的用法, Koa的文檔也很簡(jiǎn)單, 它一開始就提供了一個(gè)例子:
const Koa = require("koa"); const app = new Koa(); app.use(async ctx => { ctx.body = "Hello World"; }); app.listen(3000);
這是啟動(dòng)最基本的的web服務(wù), 這個(gè)跑起來沒啥問題.
同樣, 文檔也提供了作為Koa的核心賣點(diǎn)的中間件的基本用法:
const Koa = require("koa"); const app = new Koa(); // x-response-time app.use(async (ctx, next) => { const start = Date.now(); await next(); const ms = Date.now() - start; ctx.set("X-Response-Time", `${ms}ms`); }); // logger app.use(async (ctx, next) => { const start = Date.now(); await next(); const ms = Date.now() - start; console.log(`${ctx.method} ${ctx.url} - ${ms}`); }); // response app.use(async ctx => { ctx.body = "Hello World"; }); app.listen(3000);
上面代碼可能跟我們之前寫的js代碼常識(shí)不太符合了, 因?yàn)閍sync/await會(huì)暫停作案現(xiàn)場(chǎng), 類似同步. 也就是碰到await next, 代碼會(huì)跳出當(dāng)前中間件, 執(zhí)行下一個(gè), 最終還回原路返回, 依次執(zhí)行await next下面的代碼, 當(dāng)然這只是一個(gè)表述而已, 實(shí)際就是一個(gè)遞歸返回Promise, 后面會(huì)提到.
閱讀目標(biāo)好了. 我們知道Koa怎么用了, 那對(duì)于這個(gè)框架我們想知道什么呢. 先看一下源碼的目錄結(jié)構(gòu)好了:
注意這個(gè)compose.js是我為了方便修改源碼拉過來的, 其實(shí)它是額外的一個(gè)包.
application.js 作為入口文件肯定是個(gè)構(gòu)造函數(shù)
context.js 就是ctx咯
request.js
response.js
那我們讀源碼總需要一個(gè)目標(biāo)吧, 這篇文章里我們假定目標(biāo)就是弄懂Koa的中間件原理好了
分析執(zhí)行流程好, 目標(biāo)也有了, 下面正式進(jìn)入源碼閱讀狀態(tài). 我們以最簡(jiǎn)單的示例代碼作為入口來切入Koa的執(zhí)行過程:
const app = new Koa();
上面我們可以看到Koa是作為構(gòu)造函數(shù)引用的, 那么我們來看看入口文件Application.js 導(dǎo)出了個(gè)啥:
module.exports = class Application extends Emitter { // ... }
毫無疑問是可以對(duì)應(yīng)上的, 導(dǎo)出了一個(gè)類.
app.use(async ctx => { ctx.body = "Hello World"; });
看上面的東西似乎進(jìn)入正題了, 我們知道use就是引用了一個(gè)中間件, 那來看看use是個(gè)啥玩意:
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; }
太長(zhǎng)太臭, 精簡(jiǎn)一下
use(fn) { this.middleware.push(fn); return this; }
emm 這下就很清楚了, 就是維護(hù)了一個(gè)中間件數(shù)組middleware, 到這里不要忘了我們的目標(biāo): Koa的中間件原理, 既然找到這個(gè)中間件數(shù)組了, 我們就來看看它是怎么被調(diào)用的吧. 全局搜一下, 我們發(fā)現(xiàn)其實(shí)就一個(gè)方法里用到了middleware:
callback() { const fn = compose(this.middleware); if (!this.listeners("error").length) this.on("error", this.onerror); const handleRequest = (req, res) => { const ctx = this.createContext(req, res); return this.handleRequest(ctx, fn); }; return handleRequest; }
上面的代碼可以看到, 似乎有一個(gè)compose對(duì)middleware進(jìn)行處理了, 我們好像離真相越來越近了
function compose (middleware) { /** * @param {Object} context * @return {Promise} * @api public */ return function (context, next) { // last called middleware # let index = -1 return dispatch(0) function dispatch (i) { index = i let fn = middleware[i] if (i === middleware.length) fn = next if (!fn) return Promise.resolve() try { return Promise.resolve(fn(context, function next () { return dispatch(i + 1) })) } catch (err) { return Promise.reject(err) } } } }刪除邊界條件, 錯(cuò)誤處理
compose.js的代碼很短, 但是還是嫌長(zhǎng)怎么辦, 之前有文章提到的, 刪除邊界條件和異常處理:
function compose (middleware) { /** * @param {Object} context * @return {Promise} * @api public */ return function (context, next) { let index = -1 return dispatch(0) function dispatch (i) { index = i let fn = middleware[i] if (!fn) return Promise.resolve() return Promise.resolve(fn(context, function next () { return dispatch(i + 1) })) } } }
這么一看就清晰多了, 不就是一個(gè)遞歸遍歷middleware嘛. 似乎跟express有點(diǎn)像.
猜想結(jié)論大膽假設(shè)嘛, 前面提到了, await 會(huì)暫停執(zhí)行, 那await next 似乎暫停的就是這里, 然后不斷遞歸調(diào)用中間件, 然后遞歸中斷了, 代碼又從一個(gè)個(gè)的promise里退出來, 似乎這樣就很洋蔥了.
emm 到底是不是這樣呢, 我也不知道. 比較還想再水一篇文章呢.
文章版權(quán)歸作者所有,未經(jīng)允許請(qǐng)勿轉(zhuǎn)載,若此文章存在違規(guī)行為,您可以聯(lián)系管理員刪除。
轉(zhuǎn)載請(qǐng)注明本文地址:http://www.ezyhdfw.cn/yun/92602.html
摘要:本筆記共四篇源碼閱讀筆記源碼閱讀筆記源碼閱讀筆記服務(wù)器啟動(dòng)與請(qǐng)求處理源碼閱讀筆記對(duì)象起因前兩天閱讀了的基礎(chǔ),和中間件的基礎(chǔ)。的前端樂園原文鏈接源碼閱讀筆記服務(wù)器啟動(dòng)與請(qǐng)求處理 本筆記共四篇Koa源碼閱讀筆記(1) -- coKoa源碼閱讀筆記(2) -- composeKoa源碼閱讀筆記(3) -- 服務(wù)器の啟動(dòng)與請(qǐng)求處理Koa源碼閱讀筆記(4) -- ctx對(duì)象 起因 前兩天閱讀了K...
摘要:本筆記共四篇源碼閱讀筆記源碼閱讀筆記源碼閱讀筆記服務(wù)器啟動(dòng)與請(qǐng)求處理源碼閱讀筆記對(duì)象起因前兩天終于把自己一直想讀的源代碼讀了一遍。首先放上關(guān)鍵的源代碼在上一篇源碼閱讀筆記服務(wù)器啟動(dòng)與請(qǐng)求處理中,我們已經(jīng)分析了的作用。 本筆記共四篇Koa源碼閱讀筆記(1) -- coKoa源碼閱讀筆記(2) -- composeKoa源碼閱讀筆記(3) -- 服務(wù)器の啟動(dòng)與請(qǐng)求處理Koa源碼閱讀筆記(4...
摘要:于是抱著知其然也要知其所以然的想法,開始閱讀的源代碼。問題讀源代碼時(shí),自然是帶著諸多問題的。源代碼如下在被處理完后,每當(dāng)有新請(qǐng)求,便會(huì)調(diào)用,去處理請(qǐng)求。接下來會(huì)繼續(xù)寫一些閱讀筆記,因?yàn)榭吹脑创a確實(shí)是獲益匪淺。 本筆記共四篇Koa源碼閱讀筆記(1) -- coKoa源碼閱讀筆記(2) -- composeKoa源碼閱讀筆記(3) -- 服務(wù)器の啟動(dòng)與請(qǐng)求處理Koa源碼閱讀筆記(4) -...
摘要:前言自從上次在掘金發(fā)布年山地人的前端完整自學(xué)計(jì)劃講一個(gè)站主山地人的天前端自學(xué)故事以來,一眨眼山地人老哥在站做主已經(jīng)有天了。所以這個(gè)體系里的一些框架包括也是山地人年自學(xué)計(jì)劃的一部分。月底,山地人老哥開啟了的兩個(gè)專題。 前言 自從上次在掘金發(fā)布【2019年山地人的前端完整自學(xué)計(jì)劃——講一個(gè)B站UP主山地人的40天前端自學(xué)故事】 以來,一眨眼山地人老哥在B站做Up主已經(jīng)有85天了。 時(shí)隔一個(gè)...
摘要:正好自己之前也想看的源代碼,所以趁著這個(gè)機(jī)會(huì),一口氣將其讀完。源碼解讀的源代碼十分簡(jiǎn)潔,一共才兩百余行。結(jié)語的源代碼讀取來不難,但其處理方式卻令人贊嘆。而且閱讀的源代碼,是閱讀源碼的必經(jīng)之路。 本筆記共四篇Koa源碼閱讀筆記(1) -- coKoa源碼閱讀筆記(2) -- composeKoa源碼閱讀筆記(3) -- 服務(wù)器の啟動(dòng)與請(qǐng)求處理Koa源碼閱讀筆記(4) -- ctx對(duì)象 起...
閱讀 2855·2021-11-02 14:42
閱讀 3224·2021-10-08 10:04
閱讀 1251·2019-08-30 15:55
閱讀 1086·2019-08-30 15:54
閱讀 2380·2019-08-30 15:43
閱讀 1739·2019-08-29 15:18
閱讀 927·2019-08-29 11:11
閱讀 2429·2019-08-26 13:52