理解Koa洋蔥模型

中間件特性

|                                                                                  |
    |                              middleware 1                                        |
    |                                                                                  |
    |          +-----------------------------------------------------------+           |
    |          |                                                           |           |
    |          |                    middleware 2                           |           |
    |          |                                                           |           |
    |          |            +---------------------------------+            |           |
    |          |            |                                 |            |           |
    | action   |  action    |        middleware 3             |    action  |   action  |
    | 001      |  002       |                                 |    005     |   006     |
    |          |            |   action              action    |            |           |
    |          |            |   003                 004       |            |           |
    |          |            |                                 |            |           |

+---------------------------------------------------------------------------------------------------->html

|          |            |                                 |            |           |
    |          |            |                                 |            |           |
    |          |            +---------------------------------+            |           |
    |          +-----------------------------------------------------------+           |
    +----------------------------------------------------------------------------------+

先寫一段貫穿全文的koa的代碼git

const Koa = require('koa');
let app = new Koa();

const middleware1 = async (ctx, next) => { 
  console.log(1); 
  await next();  
  console.log(6);   
}

const middleware2 = async (ctx, next) => { 
  console.log(2); 
  await next();  
  console.log(5);   
}

const middleware3 = async (ctx, next) => { 
  console.log(3); 
  await next();  
  console.log(4);   
}

app.use(middleware1);
app.use(middleware2);
app.use(middleware3);
app.use(async(ctx, next) => {
  ctx.body = 'hello world'
})

app.listen(3001)

// 輸出1,2,3,4,5,6

await next()使每一個middleware分紅,前置操做,等待其餘中間件操做能夠觀察到中間件的特性有:github

  • 上下文ctx
  • await next()控制先後置操做
  • 後置操做相似於數據解構-棧,先進後出

promise 的模擬實現

Promise.resolve(middleware1(context, async() => {
  return Promise.resolve(middleware2(context, async() => {
    return Promise.resolve(middleware3(context, async() => {
      return Promise.resolve();
    }));
  }));
}))
.then(() => {
    console.log('end');
});

從這段模擬代碼咱們能夠知道next()返回的是promise,須要使用await去等待promise的resolve值。promise的嵌套就像是洋蔥模型的形狀就是一層包裹着一層,直到await到最裏面一層的promise的resolve值返回。segmentfault

思考:數組

  • 若是next()不加await執行順序是什麼呢?
    在這個例子裏面若是隻是next()執行順序跟await next()是同樣的,由於next的前置操做是同步的
  • 若是前置操做是異步的操做呢?promise

    const p = function(args) {
      return new Promise(resolve => {
        setTimeout(() => {
          console.log(args);
          resolve();
        }, 100);
      });
    };
    
    const middleware1 = async (ctx, next) => {
      await p(1);
      // await next();
      next();
      console.log(6);
    };
    
    const middleware2 = async (ctx, next) => {
      await p(2);
      // await next();
      next();
      console.log(5);
    };
    
    const middleware3 = async (ctx, next) => {
      await p(3);
      // await next();
      next();
      console.log(4);
    };
    // 輸出結果:1,6,2,5,3,4

    當程序執行到middleware1,執行到await p(1)等待promise值返回跳出而後到下一個事件循環時,執行next()也就是執行到middleware2,再執行到await p(2)等待promise值返回跳出middleware2,回到middleware1繼續執行console.log(6),以此類推輸出順序爲1.6.2.5.3.4app


Promise的嵌套雖然能夠實現中間件流程,可是嵌套的代碼會產生可維護性和可讀性的問題,也帶來中間件擴展的問題。koa

Koa.js中間件引擎是有koa-compose模塊來實現的,也就是Koa.js實現洋蔥模型的核心引擎。異步

koa-compose 實現

this.middleware = [];
  use(fn) {
    this.middleware.push(fn);
    ……
 }
 callback() {
    const fn = compose(this.middleware);
    ……
 }
 
function compose (middleware) {
  return function (context, next) {
    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, dispatch.bind(null, i + 1)));
      } catch (err) {
        return Promise.reject(err)
      }
    }
  }
}

Koa實現的代碼很是簡潔,咱們在使用use的時候將middleware存在一個數組裏面,當攔截到請求時執行callback方法,callback中調用了compose,compose方法使用遞歸執行中間件,遍歷完成返回promise.resolve(),實際最後執行的代碼也是上面所講的promise嵌套的形式。async

擴展:Await與Generator

一般咱們的都會說await阻塞後面的操做等待promise的resolve返回值或者其餘值,若是沒有await這個語法糖,要怎麼去實現呢?這個等待的過程是怎麼控制的呢?

Generator

Generator其實是一個特殊的迭代器

let gen = null;
function* genDemo(){
  console.log(1)
  yield setTimeout(()=>{
    console.log(3);
    gen.next();// c
  },100)
  console.log(4)
}
gen = genDemo();// a
gen.next(); // b

a. 調用generator,該函數不執行,也就是尚未輸出1,返回的是指向內部狀態的遍歷對象。
b. generator函數開始執行,輸出1,遇到第一個yeild表達式停下來,調用gen.next()返回一個對象{value: 10, done:false},這裏的value表示setTimeout的一個標識值,也就是調用clearTimeout的參數,是一個數字。done表示遍歷尚未結束。100毫秒後輸出3;
c. Generator函數從上次在yeild中止的地方一直執行到函數結束(沒有其餘的yeild),輸出4,返回{value: undefined,done:true},表示遍歷結束。

能夠看到yeild有控制代碼進度的做用,是否是跟await有殊途同歸之妙
來看下await編譯成generator形式的代碼,雖然多了一些代碼,可是咱們能夠把_asyncToGenerator(function*() {……}調用generator,把asyncGeneratorStep(gen, resolve, reject, _next, _throw, "next", value);當作是gen.next();就很容易理解了。

function asyncGeneratorStep(gen, resolve, reject, _next, _throw, key, arg) {
  try {
    var info = gen[key](arg);
    var value = info.value;
  } catch (error) {
    reject(error);
    return;
  }
  if (info.done) {
    resolve(value);
  } else {
    Promise.resolve(value).then(_next, _throw);
  }
}

function _asyncToGenerator(fn) {
  return function() {
    var self = this,
      args = arguments;
    return new Promise(function(resolve, reject) {
      var gen = fn.apply(self, args);
      function _next(value) {
        asyncGeneratorStep(gen, resolve, reject, _next, _throw, "next", value);
      }
      function _throw(err) {
        asyncGeneratorStep(gen, resolve, reject, _next, _throw, "throw", err);
      }
      _next(undefined);
    });
  };
}

const middleware1 =
  /*#__PURE__*/
  (function() {
    var _ref = _asyncToGenerator(function*(ctx, next) {
      console.log(1);
      yield next();
      console.log(6);
    });

    return function middleware1(_x, _x2) {
      return _ref.apply(this, arguments);
    };
  })();

const middleware2 =
  /*#__PURE__*/
  (function() {
    var _ref2 = _asyncToGenerator(function*(ctx, next) {
      console.log(2);
      yield next();
      console.log(5);
    });

    return function middleware2(_x3, _x4) {
      return _ref2.apply(this, arguments);
    };
  })();

const middleware3 =
  /*#__PURE__*/
  (function() {
    var _ref3 = _asyncToGenerator(function*(ctx, next) {
      console.log(3);
      yield next();
      console.log(4);
    });

    return function middleware3(_x5, _x6) {
      return _ref3.apply(this, arguments);
    };
  })();

Promise.resolve(
  middleware1(
    context,
    /*#__PURE__*/
    _asyncToGenerator(function*() {
      return Promise.resolve(
        middleware2(
          context,
          /*#__PURE__*/
          _asyncToGenerator(function*() {
            return Promise.resolve(
              middleware3(
                context,
                /*#__PURE__*/
                _asyncToGenerator(function*() {
                  return Promise.resolve();
                })
              )
            );
          })
        )
      );
    })
  )
).then(() => {
  console.log("end");
});

參考連接:
https://chenshenhai.github.io...
https://segmentfault.com/a/11...

相關文章
相關標籤/搜索
本站公眾號
   歡迎關注本站公眾號,獲取更多信息