Koajs中間件之next(二)

Koajs中間件之定義(一)
Koajs中間件之next(二)
Koajs中間件之context(三)

第一篇文章中咱們講過,「在Koa中,中間件是指連貫整個 Koa 應用程序,並共享資源的獨立插件」,注意兩個詞,「連貫」與「共享資源」,與上面的代碼一一對應,「連貫」對應「next」,「共享資源對應context」。
Koa 中經過 next 貫穿整個應用程序,下面分析一下 next 中作了什麼。javascript

中間件集合

Koa 類中的構造函數中初始化了一堆數據,其中兩個重要的,一個是「middleware」,另外一個是「context」。(非關鍵代碼使用...省略)java

constructor() {
    ...
    this.middleware = [];
    this.context = Object.create(context);
    ...
  }

全部的中間件在一個數組存放,當咱們調用「app.use」方法的時候會往數組中加入咱們自定義的中間價segmentfault

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

最後經過「koa-compose」來統一觸發中間件隊列api

callback() {
    const fn = compose(this.middleware);
    ...
    return (req, res) => {
      ...
      fn(ctx).then(() => respond(ctx)).catch(ctx.onerror);
    };
}

koa-compose

koa-compose 源碼只有短短几十行,關鍵代碼不到10行,直接貼上源碼數組

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
    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
      if (!fn) return Promise.resolve()
      try {
        return Promise.resolve(fn(context, function next () {
          return dispatch(i + 1)
        }))
      } catch (err) {
        return Promise.reject(err)
      }
    }
  }
}

若是咱們須要使用 Koa 的洋蔥模型能夠直接調用 koa-componse 來達到目的bash

const koaCompose = require('koa-compose');

const middleware1 = (ctx, next) => {
    console.log('middleware1 >>>>>');
    next();
    console.log('middleware1 <<<<<');
}
const middleware2 = (ctx, next) => {
    console.log('middleware2 >>>>>');
    next();
    console.log('middleware2 <<<<<');
}

const middleware3 = (ctx, next) => {
    console.log('middleware3 >>>>>');
    console.warn(ctx);
    next();
    console.log('middleware3 <<<<<');
}

const fn = koaCompose([middleware1, middleware2, middleware3]);

fn({ a: 'a' }, (ctx) => {
    console.warn(ctx);
    console.warn('The last next use do =======<');
    return ctx;
}).then((ctx) => {
    console.warn(ctx);
    console.warn('end =====<');
});

輸出:app

middleware1 >>>>>
middleware2 >>>>>
middleware3 >>>>>
{ a: 'a' }
{ a: 'a' }
The last next use do =======<
middleware3 <<<<<
middleware2 <<<<<
middleware1 <<<<<
undefined
end =====<

精簡 koa-componse

爲了更好的分析代碼,去除 koa-componse 代碼中的各類非關鍵判斷及異步處理邏輯後,代碼以下koa

const compose = function (middlewares) {
    // 返回一個函數,提供兩個參數,一個是傳入的上下文,另外一個是全部中間件執行完畢後回調
    return function(context, last) {
        let idx = -1;   // 初始定義當前執行中間件下標未-1,即表示當前未執行任何中間件
        
        
        return dispatch(0); // 手動觸發第1箇中間件

        function dispatch(i) {
            idx = i;    // 設置當前執行中間件下標
            let fn = middlewares[i] || last;    
            try {
                // 執行當前中間件的時候,給當前中間件參數中的next參數賦值爲一個函數,在這個函數中執行下一個中間件
                if (fn) fn(context, function() {
                    dispatch(i + 1);    // 觸發下一個中間價,而且將中間件執行下標+1
                })
            } catch (err) { // 全部的中間件執行完畢,執行最後回調
                last(context);
            }
        }
    }
};

執行代碼:異步

const run = compose([middleware1, middleware2, middleware3]);

run({ a: 'a' }, () => {
    console.warn('Middlewares do last =======<');
});

輸出:函數

middleware1 >>>>>
middleware2 >>>>>
middleware3 >>>>>
Middlewares do last =======<
middleware3 <<<<<
middleware2 <<<<<
middleware1 <<<<<

整體思路

  1. 將全部中間件推送到一個數組中
  2. 第一個中間件首先執行
  3. 在第一個中間件執行前,將第一個中間件中的 next 參數設置爲一個觸發下一個中間件執行的函數
  4. 第一個中間件調用 next 函數,把執行器交給下一個中間價
  5. 循環往復,直最後一箇中間件執行完畢
  6. 全部中間件執行完畢後,依次執行next外層代碼

優勢

  • 解決多重回調地獄模式
  • 統一處理上下文context掛載與傳遞
  • 異常捕獲

缺點

  • 當一個項目中存在多箇中間件時,對於性能會有必定影響,對於優化來講是一種考驗

參考資料

Mdn 類
Koa 官網

相關文章
相關標籤/搜索