天天看點

Koa 解析Koa 介紹自我定位中間件處理邏輯錯誤處理邏輯更小的核心代碼Koa 解析Koa 初始化應用執行個體app.use() 添加中間件app.listen() 監聽--核心邏輯解析 createContextkoa-compose 實作洋蔥圈模型解析 handleRequest總結

作者:閃念基因
Koa 解析Koa 介紹自我定位中間件處理邏輯錯誤處理邏輯更小的核心代碼Koa 解析Koa 初始化應用執行個體app.use() 添加中間件app.listen() 監聽--核心邏輯解析 createContextkoa-compose 實作洋蔥圈模型解析 handleRequest總結

在 koa 的官網上,slogan 是: 基于 node.js 平台的下一代 web 開發架構 。我們都知道, koa 與 express 都誕生自同一個團隊。如何解析這個 slogan?

該 slogan 中所有包含“比較”含義的詞彙,都可以了解為是相對 express 而言的。所謂的"下一代""更小""更快",都特有所指。我們來看看現階段 express 與 koa 的下載下傳量對比:

Koa 解析Koa 介紹自我定位中間件處理邏輯錯誤處理邏輯更小的核心代碼Koa 解析Koa 初始化應用執行個體app.use() 添加中間件app.listen() 監聽--核心邏輯解析 createContextkoa-compose 實作洋蔥圈模型解析 handleRequest總結

可以看出,express 的市場占有量與下載下傳量(以及增速)都遠遠高于 koa。是以自稱為“下一代”并不為過(當然,這個資料層面來對比是否公平,值得商榷)。

同時,與 express 内部囊括了一套基礎的業務處理架構的做法不同,koa 隻包含了被稱為“洋蔥圈模型”的中間件核心處理邏輯。koa 期望由社群來健全完善更多的業務子產品。這樣看來,将 koa 看做 express 之前内置的 connect 來對比更合适(express 4 内部已經移除 connect)。不過,“更小”的 koa 定位更加明确,其内部一些突出的特性也讓開發者感到歡喜。

接下來,我們就來詳細看看 koa 相比于 express 有什麼不同?隻有了解了不同,我們才會更加了解 koa 的特性。

在 koa 官方網頁上,有這樣一段關于 koa 定位的描述:

摘抄網絡上的一段翻譯如下:

從理念上來說,koa 意圖“修複并取代 node”,而 express 做的是“增強 node”。koa 使用 promise 和 async 函數來擺脫回調地獄并簡化異常處理邏輯。它暴露了自身的 ctx.request 和 ctx.response 對象而取代了 node 的 req 和 res 對象。

express 從另一方面,通過增加額外的屬性和方法增強了 node 的 req 和 res 對象,并引入了許多架構上的功能,例如路由和模闆,而 koa 沒有這麼做。

其實作是否符合定位,各位可自行看待。

在本質上,koa 與 express 4 的差別在于下面兩個點:

koa 是使用 promise + async/await 來進行中間件傳遞邏輯;而 express 4 是使用回調函數來進行中間件傳遞邏輯。

koa 是走完所有的中間件處理邏輯才最終傳回請求;而 express 4 是遇到 res.send() 就傳回請求。

借助于新 es 規範中 async/await 寫法的 promise 支援,koa 能夠完美支援異步處理方式與擺脫回調地獄。下面的模拟實作,看代碼的差別就可以體會到兩者的不同之處。

在 koa 内部,由于是洋蔥圈模型,正常的中間件處理過程可以不處理報錯邏輯,隻需要在中間件數組的第一個中間件設定為錯誤函數中間件即可。而在 express 4 中,需要在每一個正常的中間件中都包含錯誤處理邏輯,并且需要通過 next() 函數抛出來,否則該錯誤将會被隐沒。

與 express 4 包含有齊整的 web 工具(路由、模闆等)不同,koa 隻專注于核心代碼。是以它的體積更小。

簡單的描述并不能讓我們對 koa 的原理有更深的了解。我們嘗試來看看源碼。最簡單的示範代碼:

在這裡,我們想要去源碼上弄明白幾個事情:

koa 應用中,我們通過 const app = new koa() 來建構一個執行個體應用。核心代碼(有删減):

初始化應用執行個體的過程可以看出:為 app 執行個體添加 context 、 request 、 response 、 middleware 等屬性。 在這個初始化的過程中,koa 會把 koa 官方提供的方法都挂載到相應的位置,友善在代碼中調用。

在這裡會檢查傳入函數的類型,如果是老的 generator 函數類型會轉換一下,然後直接放到 middleware 這個數組中。數組中的中間件,會在每一個請求中去挨個執行一遍。

在 listen 函數執行的時候,才會建立 server :

在 node 的 http 子產品,對于每一個請求,都會走到回調函數 callback 中去。是以這個 callback 是用于處理實際請求的。我們來看看 callback 做了啥:

這裡的内容并不簡單,涉及到幾個點:

在這裡主要是明确 context 挂載的内容,這個 context 是 每一次請求都會生成 的。由代碼可知:

每個應用執行個體 app 都會有對應的 context 、 request 、 response 執行個體(即 this.xxx )。每個請求,都會基于這些執行個體來建立自己的執行個體。這樣做的目的是為了不污染全局的變量。

将 node 原生的 req 、 res 以及 this 挂載到 context 、 request 、 response 上。

将建立的 context 傳回,傳入所有中間件的第一個參數,作為這個請求的上下文。

着重解釋一下第 2 點,為什麼要把這些屬性挂載上去。除了便利性外,我們通過檢視源碼 resquest.js response.js 檔案還可以知道:所有的這些通路都是代理,最終通路的還是 node 原生的 req 、 res 。

接下來,我們來看看源代碼中的 koa-compose 插件是如何實作洋蔥圈模型:

簡單來看,在 compose 中會根據變量 i 挨個執行中間件,以遞歸的方式來執行。 整個過程不難了解,實作較為巧妙。 值得注意的是:每一次 dispatch() 的傳回值都是 promise 。可參考下面的動圖來過一遍上面代碼的實作。

Koa 解析Koa 介紹自我定位中間件處理邏輯錯誤處理邏輯更小的核心代碼Koa 解析Koa 初始化應用執行個體app.use() 添加中間件app.listen() 監聽--核心邏輯解析 createContextkoa-compose 實作洋蔥圈模型解析 handleRequest總結

我們來看看,得到了每個請求的上下文資訊 context 與 經過 compose 後的中間件邏輯之後,我們來看看最終如何處理請求的:

執行 compose 後的中間件函數,最後執行 respond 方法。經檢視,可以知道,主要是對響應進行封裝,并且指派給到 context.res.end(body) 。

綜上,我們簡單地對 koa 進行了介紹;同時通過解析 demo 檔案的執行過程,我們從源碼級别窺見了 koa 的一些相關原理實作。至此,我們就已經将 koa 相關的介紹與解析講完了。我在寫下來本文章的過程中,會對整個過程更加了解,也希望對你有所幫助!

繼續閱讀