一步步去閱讀koa源碼,中間件執行原理

koa的中間件執行的流程控制,代碼的是很是精妙的。由下面的一張洋蔥模型的圖來形容,記住這張圖。git

爲何是這樣子的圖,下面咱們有一個例子來描述一下github

const Koa = require('koa')
const app = new Koa()
// fn1
app.use(async (ctx, next) => {
  console.log('fn1-1')
  next()
  console.log('fn1-2')
})

// fn2
app.use(async (ctx, next) => {
  console.log('fn2-1')
  next()
  console.log('fn2-2')
})

// fn3
app.use(async (ctx, next) => {
  console.log('fn3-1')
  next()
  console.log('fn3-2')
})

app.listen(4002)

// fn1-一、fn2-一、fn3-一、fn3-二、fn2-二、fn1-2

上面的這個例子,順序打印出來的是fn1-一、fn2-一、fn3-一、fn3-二、fn2-二、fn1-2,如今只知道,調用next()函數就會把控制流程就跳到下一個中間件,知道執行全部完以後而後再逐步向上執行前一個next後面的代碼。這根跟洋蔥有很大的相像似性(若是你願意一層一層一層的剝開個人心~~~)。數組

探索

可是其中的原理是什麼呢??下面咱們一步步去探索。app

首先是調用 app.use(fn) 這行代碼,這行代碼在源碼裏面,刪除一些代碼判斷,是這樣子的koa

constructor() {
  super();
  this.middleware = [];
}

use(fn) {
  this.middleware.push(fn);
  return this;
}

就是把全部函數push到一個middleware的數組之中,這個use就是專門幹這中勾當的。async

好了知道use的做用了,執行了use以後 咱們的middleware中就有不少中間件函數了,下面咱們繼續看下去。函數

而後執行到 app.listen函數以後,代碼以下ui

listen(...args) {
  // 建立一個server
  const server = http.createServer(this.callback());
  return server.listen(...args);
}

咱們看到裏麼有個this.callback()執行函數,而後咱們跳到這個函數裏面。this

callback() {
  // 咱們看這裏
  const fn = compose(this.middleware);

  const handleRequest = (req, res) => {
    const ctx = this.createContext(req, res);
    // 這個節點咱們請記住下面這一行代碼
    return this.handleRequest(ctx, fn);
  };

  return handleRequest;
}

這個callback函數裏面,執行了compose函數,而且把middleware數組做爲參數傳遞進去。spa

執行到了compose函數,下面咱們就看看compose裏面有什麼。

compose函數就是一開始引用了koa-compose模塊,簡化以後發現裏面的代碼以下,簡化後就簡簡單單的20幾行代碼,後面會詳細解釋下面的代碼。

function compose (middleware) {
  return function (context, next) {
    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返回一個函數,這也是最核心的一個函數。注意這是上面的callback調用的。獲得一個fn函數

看上面的callback調用的

而後執行到this.handleRequest(ctx, fn); 這個函數吧ctxfn(這個就是上面compose返回的函數)做爲參數,傳入到this.handleRequest中。 代碼以下。

handleRequest(ctx, fnMiddleware) {
  return fnMiddleware(ctx).then(handleResponse).catch(onerror);
}

到這裏才真正的執行了compose返回的函數,把ctx傳進去。而後咱們繼續看這個函數fnMiddleware(ctx),其實就是下面這樣子的。

function (context, next) {
  // 設置了一個出事索引值
  let index = -1
  // 調用dispatch函數,開始時候傳0進去
  return dispatch(0) 

  // 聲明dispatch函數,
  function dispatch (i) {
    // 把傳進來的賦值給index
    index = i
    // 拿出middleware第i箇中間件函數,賦值給fn
    let fn = middleware[i] 
    // 判斷若是i 等於middleware的長度 就把next 賦值給 fn
    if (i === middleware.length) fn = next
    // 若是fn是假的 return return Promise.resolve()
    if (!fn) return Promise.resolve()
    try {
      // 返回一個Promise.resolve, 同時執行fn, 也就是中間件,把next 函數傳遞到fn裏面 
      return Promise.resolve(fn(context, function next () {
        // 遞歸調用本身
        return dispatch(i + 1)
      }))
    } catch (err) {
      return Promise.reject(err)
    }
  }
}

上面的代碼是這個部分的精華。這裏詳細的說一下,首先定義了一個indexdispatch函數, 而後一開始調用dispatch(0)函數,裏面把0賦值給了index,而後從middleware的數組(例子中咱們有三個中間件函數)中拿到第0箇中間件函數,賦值給fn,通過兩個if都不符合條件,而後執行

return Promise.resolve(fn(context, function next () {
    // 遞歸調用本身
    return dispatch(i + 1)
  }))

這裏的執行fn 中間件函數,而且把ctx function next () { return dispatch(i + 1) }) 做爲參數傳遞進去。這個時候代碼以下一幕瞭然

app.use(async (ctx, next) => {
  console.log('fn1-1')
  next() // 執行傳入的next
  console.log('fn1-2')
})

執行這個函數 就會打印出fn1-1 而後就會執行next()函數,看上上一塊代碼,執行next()函數裏面會調用 dispatch(i + 1) 也就是調用第fn = middleware[1] 正是第二個中間件。

看到這裏你們就大概明白了。而後進入第二個中間件執行fn,打印出fn2-1,繼續執行next()函數,next函數裏面繼續調用 dispatch(i + 1)

也就是fn = middleware[2] 第三中間件函數,打印出fn3-1,繼續執行next()函數裏面會調用 dispatch(i + 1),也就是fn = middleware[3]

這裏注意了,if (i === middleware.length) fn = next到這裏會符合這個條件,而後把next 賦值給fn 這裏的next就是這個fnMiddleware(ctx).then(handleResponse).catch(onerror);調用時候傳入的,然而這裏並無傳入,因此這時候 fn 就是 undefined,而後繼續執行到if (!fn) return Promise.resolve() 返回一個空的值,這就是第三個中間件的next執行結果,

而後繼續執行下一行就打印出了fn3-2,最後向上執行到fn2-2,而後到fn1-2, 整個中間件的執行過程。很像洋蔥模型,一層層進入,而後一層層出來。

好了整個中間件執行過程就是醬紫啦~~~

最後安利一波博客: https://github.com/naihe138/n...

相關文章
相關標籤/搜索