1. 程式人生 > >koa2第二篇: 圖解中介軟體原始碼執行過程

koa2第二篇: 圖解中介軟體原始碼執行過程

中介軟體

洋蔥模型

首先寫一個簡單的中介軟體demo:

const Koa = require('koa')
const app = new Koa()
const port = 3000

const ctx1 = async (ctx, next) => {
    console.log('開始執行中介軟體1')
    await next()
    ctx.response.type = 'text/html'
    ctx.response.body = '<h3>hello world</h3>'
    console.log('結束執行中介軟體1'
) } app.use(ctx1) app.use(async function ctx2 (ctx, next) { console.log('開始執行中介軟體2') await next() console.log('結束執行中介軟體2') }) app.listen(port, () => { console.log(`server is running on the port: ${port}`) }) 複製程式碼

很明顯中介軟體執行順序是這樣的:

開始執行中介軟體1
開始執行中介軟體2
結束執行中介軟體2
結束執行中介軟體1
複製程式碼

你可以理解為koa2會先按照中介軟體註冊順序執行next()之前的程式碼, 執行完到底部之後, 返回往前執行next()之後的程式碼。

重點是我們需要koa2原始碼究竟是怎麼樣執行的? 現在開始除錯模式進入koa2原始碼一探究竟。

  • 首先在兩個中介軟體註冊的地方打了斷點

  • 我們可以看到koa2是先按照你中介軟體的順序去註冊執行

  • 然後會進入callback. 這是因為
// 應用程式
app.listen(port, () => {
    console.log(`server is running on the port: ${port}`)
})

// 原始碼
 listen(...args) {
    debug('listen'
); const server = http.createServer(this.callback()); return server.listen(...args); } 複製程式碼

這個時候this.middleware已經存了兩個中介軟體。

  • 這個時候你請求一個路由比如
http://localhost:3000/a
複製程式碼

koa2的中介軟體處理就是在這個函式裡面

callback() {
    // compose()這是處理中介軟體的執行順序所在
  }
複製程式碼

於是我們進入這個koa-compose的原始碼看下:

'use strict'

/**
 * Expose compositor.
 */

module.exports = compose

/**
 * Compose `middleware` returning
 * a fully valid middleware comprised
 * of all those which are passed.
 *
 * @param {Array} middleware
 * @return {Function}
 * @api public
 */

function compose (middleware) {
  // 首先是一些中介軟體格式校驗
  if (!Array.isArray(middleware)) throw new TypeError('Middleware stack must be an array!')
  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
   */

  return function (context, next) {
    // last called middleware #
    let index = -1
    // 返回一個函式, 從第一個中介軟體開始執行, 可以通過next()呼叫後續中介軟體
    return dispatch(0)
    // dispatch始終返回一個Promise物件
    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 {
        // next即就是通過dispatch(i+1)來執行下一個中介軟體
        return Promise.resolve(fn(context, dispatch.bind(null, i + 1)));
      } catch (err) {
        // 捕獲中介軟體中發生的異常
        return Promise.reject(err)
      }
    }
  }
}

複製程式碼

此時i=0取出第一個中介軟體,由於閉包原因i是一直存在的。

這個時候可以看到fn就是ctx1。

注意

// next即就是通過dispatch(i+1)來執行下一個中介軟體
dispatch.bind(null, i + 1)
複製程式碼

這個時候開始進入第一個中介軟體執行第一句console.log('開始執行中介軟體1')

這裡也能看到next指的就是前面提到的dispatch.bind。

然後我們繼續單步除錯進入這句

// ctx1中的
await next()
複製程式碼

此時又重新進入compose(), 繼續執行下一個中介軟體, i=1

取出第二個中介軟體函式ctx2。

此時進入第二個中介軟體ctx2開始執行console.log('開始執行中介軟體2')

繼續單步除錯

此時i=2,fx=undefined

// 這個洋蔥模型的最後做一個兜底的處理
if (!fn) return Promise.resolve()
複製程式碼

執行中介軟體ctx2的第二句console