koa2 整體流程原理淺析(二) 之 中間件原理

koa 的中間件機制巧妙的運用了閉包和 async await 的特色,造成了一個洋蔥式的流程,和 JS 的事件流 (捕獲 -> target -> 冒泡) 類似
handleRequest(ctx, fnMiddleware) {
    const res = ctx.res;
    res.statusCode = 404;
    const onerror = err => ctx.onerror(err);
    const handleResponse = () => respond(ctx);
    onFinished(res, onerror);
    return fnMiddleware(ctx).then(handleResponse).catch(onerror);
  }
上述代碼是 request 事件的句柄,也就是說每個請求到來,都會執行這個總方法
  • onerror 爲請求設置了錯誤處理的方法
  • handleResponse 是當中間件完成後給瀏覽器返回 response 的方法,裏面是原生的 res.end(body)
  • onFinished 是判斷請求最終有沒有完成,根據不一樣的結果採起不一樣的策略
  • fnMiddleware(ctx) 就是執行全部中間件函數,而後返回一個 Promise 對象,不出錯的話執行 handleResponse

洋蔥式的中間件

值得一提的是,中間件原理的代碼並無放在 koa 中,而是單獨打了一個模塊,叫作 == koa-compose==
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
      
      // 返回給 next()
      if (!fn) return Promise.resolve() 
      try {
      
        // 返回給 next(),最外一層返回給 fnMiddleware(ctx).then(handleResponse)
        return Promise.resolve(fn(context, function next () { 
        
          // 返回給外一層 fn 的 await
          return dispatch(i + 1) 
        }))
      } catch (err) {
        return Promise.reject(err)
      }
    }
  }
  • 執行一次 dispatch 就是執行一箇中間件,算是洋蔥的一層
  • 每一個 dispatch 都會返回一個 Promise.resolve 給外面一層的 await(除了第一次,他返回給的是 fnMiddleware(ctx).then(handleResponse)
  • 每一個 dispatch 都有一個本身的序號,也就是參數 i (他用閉包控制住了) ,從 0 開始
  • 閉包裏有一個 index,是記錄執行過的中間件數量。一旦有序號大於數量,說明有中間件執行了兩次 await next,這是不被容許的
  • 每一層用 Promise.resolve 包裹是由於 await 須要接收一個 Promise 對象
下面就是中間件原理的展開寫法,仔細琢磨吧
function dispatch(0){ // 第一層的序號
        return Promise.resolve(async function a0(){
            cnosole.log('0-0')
            await 111(function next0(){
                return (function dispatch(1){ // 第二層的序號
                    return Promise.resolve(async function a1(){
                        cnosole.log('1-0')
                        await 222(function next1(){
                            return (function dispatch(2){ // 第三層的序號
                                return Promise.resolve(async function a2(){
                                    cnosole.log('2-0')
                                    await 333(function next2(){
                                        return (function dispatch(3){ // i == middleware.length ,算是洋蔥芯吧
                                        
                                            // fn[3] == undefined,說明中間件已經到洋蔥的最裏面了,開始向外返回
                                            return Promise.resolve()
                                        })()
                                    })()333
                                    console.log('2-1')
                                })
                            })()
                        })()222
                        console.log('1-1')
                    })
                })()
            })()111
            console.log('0-1')
        })
    }    

    dispatch(0).then(handleResponse)

思考

1. 普通函數採用 dispatch 算法也能取得洋蔥式的流程,爲什麼要使用 async ?

app.use(async function (ctx,next) {
    console.log('1-1')
    await new Promise(function(resolve, reject){
        setTimeout(function () {
            console.info

            ("wait for 10 mini seconds.");
            resolve();
        },10);
    });
    console.log('1-2')
    next();
    console.log('1-3')
})

app.use(async function (ctx,next) {
    console.log('2-1')
    await new Promise(function(resolve){
        setTimeout(function () {
            console.info

            ("wait for 10 mini seconds");
            resolve();
        },10);
    });
    console.log('2-2')
    next();
    console.log('2-3')
})
試試 next() 前面加上 await 和不加 await 的區別就明白了

2. 爲什麼要用 Promise.resolve 返回

由於他是洋蔥式的層級,若是用普通的 Boolean 返回的話,只能返回到上一層,無法全局獲取,對錯誤的把控難以控制。Promise 任何一層報錯,都能用 catch 捕獲

總結

koa 是一個很是輕量級的框架,只實現了中間件處理流程和對 res、req 對象的封裝。其餘的功能都由外部中間件提供。代碼不是不少,可是很精妙,對於代碼能力的提升有不小的幫助

END

相關文章
相關標籤/搜索