摘要:最近一年零零散散看了不少開源項目的源碼多少也有點心得這里想通過這篇文章總結一下這里以為例前段時間其實看過的源碼但是發現理解的有點偏差所以重新過一遍不得不說閱讀的代碼真的收獲很大沒啥奇技淫巧代碼優雅設計極好注釋什么的就更不用說了總之還是推薦把
最近一年零零散散看了不少開源項目的源碼, 多少也有點心得, 這里想通過這篇文章總結一下, 這里以Koa為例, 前段時間其實看過Koa的源碼, 但是發現理解的有點偏差, 所以重新過一遍.
不得不說閱讀tj的代碼真的收獲很大, 沒啥奇技淫巧, 代碼優雅, 設計極好. 注釋什么的就更不用說了. 總之還是推薦把他的項目都過一遍(逃)
跑通例子Koa作為一個web框架, 我們要去閱讀它的源碼肯定是得知道它的用法, Koa的文檔也很簡單, 它一開始就提供了一個例子:
const Koa = require("koa"); const app = new Koa(); app.use(async ctx => { ctx.body = "Hello World"; }); app.listen(3000);
這是啟動最基本的的web服務, 這個跑起來沒啥問題.
同樣, 文檔也提供了作為Koa的核心賣點的中間件的基本用法:
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代碼常識不太符合了, 因為async/await會暫停作案現場, 類似同步. 也就是碰到await next, 代碼會跳出當前中間件, 執行下一個, 最終還回原路返回, 依次執行await next下面的代碼, 當然這只是一個表述而已, 實際就是一個遞歸返回Promise, 后面會提到.
閱讀目標好了. 我們知道Koa怎么用了, 那對于這個框架我們想知道什么呢. 先看一下源碼的目錄結構好了:
注意這個compose.js是我為了方便修改源碼拉過來的, 其實它是額外的一個包.
application.js 作為入口文件肯定是個構造函數
context.js 就是ctx咯
request.js
response.js
那我們讀源碼總需要一個目標吧, 這篇文章里我們假定目標就是弄懂Koa的中間件原理好了
分析執行流程好, 目標也有了, 下面正式進入源碼閱讀狀態. 我們以最簡單的示例代碼作為入口來切入Koa的執行過程:
const app = new Koa();
上面我們可以看到Koa是作為構造函數引用的, 那么我們來看看入口文件Application.js 導出了個啥:
module.exports = class Application extends Emitter { // ... }
毫無疑問是可以對應上的, 導出了一個類.
app.use(async ctx => { ctx.body = "Hello World"; });
看上面的東西似乎進入正題了, 我們知道use就是引用了一個中間件, 那來看看use是個啥玩意:
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; }
太長太臭, 精簡一下
use(fn) { this.middleware.push(fn); return this; }
emm 這下就很清楚了, 就是維護了一個中間件數組middleware, 到這里不要忘了我們的目標: Koa的中間件原理, 既然找到這個中間件數組了, 我們就來看看它是怎么被調用的吧. 全局搜一下, 我們發現其實就一個方法里用到了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; }
上面的代碼可以看到, 似乎有一個compose對middleware進行處理了, 我們好像離真相越來越近了
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) } } } }刪除邊界條件, 錯誤處理
compose.js的代碼很短, 但是還是嫌長怎么辦, 之前有文章提到的, 刪除邊界條件和異常處理:
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) })) } } }
這么一看就清晰多了, 不就是一個遞歸遍歷middleware嘛. 似乎跟express有點像.
猜想結論大膽假設嘛, 前面提到了, await 會暫停執行, 那await next 似乎暫停的就是這里, 然后不斷遞歸調用中間件, 然后遞歸中斷了, 代碼又從一個個的promise里退出來, 似乎這樣就很洋蔥了.
emm 到底是不是這樣呢, 我也不知道. 比較還想再水一篇文章呢.
文章版權歸作者所有,未經允許請勿轉載,若此文章存在違規行為,您可以聯系管理員刪除。
轉載請注明本文地址:http://specialneedsforspecialkids.com/yun/92602.html
摘要:本筆記共四篇源碼閱讀筆記源碼閱讀筆記源碼閱讀筆記服務器啟動與請求處理源碼閱讀筆記對象起因前兩天閱讀了的基礎,和中間件的基礎。的前端樂園原文鏈接源碼閱讀筆記服務器啟動與請求處理 本筆記共四篇Koa源碼閱讀筆記(1) -- coKoa源碼閱讀筆記(2) -- composeKoa源碼閱讀筆記(3) -- 服務器の啟動與請求處理Koa源碼閱讀筆記(4) -- ctx對象 起因 前兩天閱讀了K...
摘要:本筆記共四篇源碼閱讀筆記源碼閱讀筆記源碼閱讀筆記服務器啟動與請求處理源碼閱讀筆記對象起因前兩天終于把自己一直想讀的源代碼讀了一遍。首先放上關鍵的源代碼在上一篇源碼閱讀筆記服務器啟動與請求處理中,我們已經分析了的作用。 本筆記共四篇Koa源碼閱讀筆記(1) -- coKoa源碼閱讀筆記(2) -- composeKoa源碼閱讀筆記(3) -- 服務器の啟動與請求處理Koa源碼閱讀筆記(4...
摘要:于是抱著知其然也要知其所以然的想法,開始閱讀的源代碼。問題讀源代碼時,自然是帶著諸多問題的。源代碼如下在被處理完后,每當有新請求,便會調用,去處理請求。接下來會繼續寫一些閱讀筆記,因為看的源代碼確實是獲益匪淺。 本筆記共四篇Koa源碼閱讀筆記(1) -- coKoa源碼閱讀筆記(2) -- composeKoa源碼閱讀筆記(3) -- 服務器の啟動與請求處理Koa源碼閱讀筆記(4) -...
摘要:前言自從上次在掘金發布年山地人的前端完整自學計劃講一個站主山地人的天前端自學故事以來,一眨眼山地人老哥在站做主已經有天了。所以這個體系里的一些框架包括也是山地人年自學計劃的一部分。月底,山地人老哥開啟了的兩個專題。 前言 自從上次在掘金發布【2019年山地人的前端完整自學計劃——講一個B站UP主山地人的40天前端自學故事】 以來,一眨眼山地人老哥在B站做Up主已經有85天了。 時隔一個...
摘要:正好自己之前也想看的源代碼,所以趁著這個機會,一口氣將其讀完。源碼解讀的源代碼十分簡潔,一共才兩百余行。結語的源代碼讀取來不難,但其處理方式卻令人贊嘆。而且閱讀的源代碼,是閱讀源碼的必經之路。 本筆記共四篇Koa源碼閱讀筆記(1) -- coKoa源碼閱讀筆記(2) -- composeKoa源碼閱讀筆記(3) -- 服務器の啟動與請求處理Koa源碼閱讀筆記(4) -- ctx對象 起...
閱讀 2772·2021-11-02 14:42
閱讀 3163·2021-10-08 10:04
閱讀 1184·2019-08-30 15:55
閱讀 1026·2019-08-30 15:54
閱讀 2311·2019-08-30 15:43
閱讀 1680·2019-08-29 15:18
閱讀 863·2019-08-29 11:11
閱讀 2362·2019-08-26 13:52