【進階全棧-02】:Koa源碼分析(中間件執行機制、Koa2與Koa1比較)

1、why Koa

  1. Koa是由Express原班人馬打造,可是相較於Express的大而全,Koa是小而精的。Koa沒有綁定不少的框架以及插件,更容易讓咱們進行擴展,包括如今較爲流行的EggJS and ThinkJS都是基於Koa開發的。
  2. Koa避免了Express中間件基於callback形式的調用,它使用了咱們JS新版本特性,Koa1中間件藉助於咱們的co and generator特性,Koa2藉助了Promise and async await特性,更好的進行流程控制以及catch咱們的錯誤
  3. Koa提供了Context對象,其實是對咱們node中request and response對象的封裝,咱們不須要不少的手動處理咱們的request and response對象。Context是貫穿咱們整個請求的過程,咱們能夠中間件須要傳遞參數掛在到Context對象上。(栗子:咱們能夠將用戶信息掛在它上面,經過ctx.state.user進行操做。)
  4. Koa的中間件執行機制:洋蔥圈模型它不是按順序執行的,多箇中間件會造成一個先進後出的棧結構,當前中間件掌握下一個中間件的執行權,對於流程控制以及後置處理邏輯的實現很是有效

2、Koa2與Koa1的比較

  1. 中間件的管理方式Koa1藉助co and generator管理咱們的中間件,Koa2藉助async await(async函數返回的是Promise對象)管理咱們的中間件。
  2. context對象獲取Koa1經過this對象(this.req,this.res)獲取,Koa2經過ctx參數(ctx.req, ctx.res)獲取。
  3. 社區成熟度Koa2的輪子多且成熟,生態比Koa1豐富。

3、舉個栗子,剖析源碼

1. 栗子

image.png
這段代碼呢,經過 listen方法建立了咱們的http服務器,端口是 3000。而且經過 use方法傳入了三個 koa中間件, 而且 console出咱們的執行結果。

2. 從listen方法提及

  1. 首先咱們去github上clone最新的Koa的源代碼。而後源碼結構以下:

image.png

  1. 能夠看得出來整個源碼的核心代碼皆在lib文件,咱們在栗子中require('koa')其實是引入的lib/application.js裏面的Application類。下面咱們來分析一下咱們的listen方法的實現,首先咱們看一下這個Application類裏面到底有些什麼:

image.png

  1. 咱們能夠看到Application類下有咱們的listen方法,方法以下:

image.png
能夠看出這裏實際上 仍是經過咱們Node.js的http模塊,經過createServer建立一個http服務器, 而後listen方法接收咱們的端口,這和咱們使用原生Node.js建立一個http服務器是同樣的。 這個this.callback()也就是咱們createServer的入參,這個參數是一個函數,是做爲 request 事件的監聽函數,這個函數還接收兩個參數req、res,也就是咱們的請求對象以及響應對象。那咱們能夠推斷出這個 listen方法裏面的 this.callback()實際上也返回是一個函數,這個函數接收req、res兩個入參。

  1. 接下來咱們再看一下這個this.callback()裏的callback方法是啥樣的:

image.png
callback方法內的第一句fn = compose(this.middleware),這個this.middleware是咱們在Application類中的constructor就定義了,是一個數組,這裏實際上存儲的就是咱們舉的栗子中的app.use()的入參方法,也就是咱們Koa的中間件。 這個compose方法很重要,主要是控制咱們的中間件的執行,下文講中間件的執行機制會仔細說說這個compose。而後看這個函數返回,果真和咱們推斷的同樣, 這個callback方法最終返回的是一個handleRequest方法,接收req,res兩個參數。handleRequest方法中,第一句this.createContext方法實際上就是對咱們req、res對象的封裝
image.png
經過createContext 建立了咱們ctx對象。 這個callback方法返回的handleRequest方法第二句是返回了Application類上的handleRequest方法的執行結果(注意這裏有「重名」的方法,注意區分),方法以下:
image.png

這個方法接收兩個參數: 一個是咱們的compose的返回結果fn、另外一個是咱們的ctx對象,這個方法主要是對咱們請求的處理以及錯誤的統一捕獲以及處理javascript

3. 執行一下咱們的栗子:扒一扒中間件的執行機制

Koa的中間件執行機制有個形象的稱呼-洋蔥圈模型。咱們先執行栗子代碼,看看輸出的結果究竟是什麼?     前端

image.png

4. 理解use方法

image.png
app.use實際上就是調用了Applcation類下面的use方法,這個方法主要是先判斷入參形式是不是一個函數等(參數校驗),若是入參是一個generator函數,這也就是koa1中的寫法,在koa2中須要koa-convert去轉換一下。而後 將咱們入參傳入的中間件函數push到this.middleware數組中

5. 中間件執行機制的核心就是compose

再回到上文提到的callback函數,咱們的中間件執行機制的核心就是compose(this.middleware),下面咱們來分析一下compose函數的源碼: java

image.png

  1. 先校驗咱們middleware的參數正確性,是不是數組,數組項是否爲函數;
  2. compose函數其實是返回一個function,這個返回的function,在上面也說起,最終是傳入到handleRequest方法中而後傳入ctx參數:fnMiddleware(context)。
  3. 再回到咱們的compose中return的這個function,接收兩個參數:第一個就是咱們handleRequest方法傳入的ctx對象,第二個next呢,其實是傳入的一個方法,這個方法是在全部middleware執行完畢後,最後執行處理的函數。這個function核心就是遞歸執行咱們的middleware。要理解這段代碼,先要理解Promise.reslove()
// Promise.reslove返回一個fulfillled狀態的promise對象
// 能夠當作new Promise()的快捷方式

Promise.reslove(fn(context, dispatch.bind(null, i+1)));
// 其實是等於
new Promise((relove, reject) => {
  reslove(fn(context, dispatch.bind(null, i+1)));
})
複製代碼
  1. 仔細閱讀這個function, 它是先定義了一個index(利用咱們的閉包每次執行一次dispatch方法去改變index),執行了dispatch(0),這個dispatch方法就是咱們執行機制實現的核心,dispatch函數裏面若是沒有執行到最後一個middleware,就返回了Promise.reslove(fn(context, dispatch.bind(null, i + 1))), 這個fn(context, dispatch.bind(null, i + 1))也就是執行咱們經過app.use加入的middleware函數,middleware函數統一接收兩個參數一個是context,一個是next:下一個middleware函數,這樣能夠看出來若是咱們koa中某個中間件沒有執行next方法,那麼以後加入的中間件是不會執行的。這也就造成了咱們的洋蔥圈模型
// 核心方法:遞歸調用咱們middlewares, 基於Promise進行異步流程控制;
// Promise.resolve()返回的是一個thenable對象;
// 因此咱們koa2中中間件都基於async函數,await等待下箇中間件方法的執行;
return function (context, next) {
    // last called middleware #
    let index = -1
    return dispatch(0)
    function dispatch (i) {
      if (i <= index) return Promise.reject(new Error('next() called multiple times'))
      index = i
      let fn = middleware[i]
      if (i === middleware.length) fn = next
      if (!fn) return Promise.resolve()
      try {
        return Promise.resolve(fn(context, dispatch.bind(null, i + 1)));
      } catch (err) {
        return Promise.reject(err)
      }
    }
  }
複製代碼

4、總結

  1. Koa這個框架是小而精美的,它的源碼是也很是少,只有一千多行。中間件的執行機制主要是讀懂這個koa-compose的代碼,多箇中間件會造成一個先進後出的棧結構,當前中間件掌握下一個中間件的執行權
  2. Koa自己的功能多是知足不了咱們平常開發的需求的,咱們能夠經過許多的第三方的包,也能夠自定義中間件來輔助咱們的開發。(瞭解了中間件機制,自定義一箇中間件就很是容易了)
  3. Koa1藉助co and generator管理咱們的中間件,Koa2藉助async await(async函數返回的是Promise對象)管理咱們的中間件

5、Koa2實戰

這是一個全棧開發實戰實例:koa2-mysql-sequelize-JWT(供參考交流,一塊兒學習)。node

6、前端修煉指南(但願能夠對您有幫助)

這是一個我的博客(前端修煉指南):front-end-Web-developer-interviewmysql

6、參考文檔

  1. Koa2 還有多久取代 Express
  2. Koa vs Express && Koa1 vs Koa2
相關文章
相關標籤/搜索