koa2 總體流程原理淺析(二) 之 中介軟體原理

Elias發表於2019-02-16

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

 
 

相關文章