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);
這個函數吧ctx
和fn
(這個就是上面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) } } }
上面的代碼是這個部分的精華。這裏詳細的說一下,首先定義了一個index
和dispatch
函數, 而後一開始調用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...