淺析KOA(2)

KOA的流轉控制

上一篇分析了co3.x版本的原理,因爲co從4.0採用es6的標準promise來實現,簡要介紹下:html

( 須要你對 promise 有必定的瞭解 )node

在這裏,yield的返回對象從thunk方法變成promise對象,因爲它們都接受方法做爲參數,這樣generator便能經過這個回調方法來控制,持續迭代下去,這裏給個示範代碼:es6

function core(genfunc) {
  var g = genfunc();

  var next = function (res) {
    // 如今res.value是promise,co內部對它其餘數據類型包裝成promise
    res.value.then(function (res) { 
      next(gen.next(res));
    }, function () {
      gen.throw(err);
    });
  };

  next(gen.next());
}

這裏,每次生成器返回的對象變成了promise,而後咱們在promise的resolve方法中遞歸調用next方法,這樣生成器就能夠持續迭代下去了. (這裏沒加終止判斷和異常處理)數據庫

因爲以前的版本中,有不少使用了thunk的包裝方法,爲了保持兼容,co中對此作了判斷,若是res.value不是promise,而是thunk,它會作兼容處理,對此咱們不用修改以前的代碼,這裏是示範:promise

function thunkToPromise(fn) {
  return new Promise(function (resolve, reject) {
    fn(function(err, res) {
      resolve(res);
    });
  });
}

對於co支持的其餘數據類型的封裝,我就不介紹了,感興趣的能夠去看co的源碼。app


好了,長篇大論鋪墊了這麼久,該說說koa了,咱們一般使用koa的時候都是經過use添加一個genfunc,因此先看看use作了什麼:koa

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

它什麼也沒作,只是將參數保存起來,而後返回引用,以便支持鏈式調用,接下來咱們看看koa的啓動:oop

app.listen = function(){
  var server = http.createServer(this.callback());
  return server.listen.apply(server, arguments);
};

這個和node的官方http示範很像,沒什麼要解釋的,再看看callback方法:this

app.callback = function(){
  var mw = [respond].concat(this.middleware);
  var gen = compose(mw);
  var fn = co.wrap(gen);
  var self = this;

  if (!this.listeners('error').length) this.on('error', this.onerror);

  return function(req, res){
    res.statusCode = 404;
    var ctx = self.createContext(req, res);
    onFinished(res, ctx.onerror);
    fn.call(ctx).catch(ctx.onerror);
  }
};

這裏,在調用co以前,它採用compose方法對以前咱們註冊的回調作了一次處理,compose是koa-compose包中的方法,這是源碼:code

function compose(middleware){
  return function *(next){
    var i = middleware.length;
    var prev = next || noop();
    var curr;

    while (i--) {
      curr = middleware[i];
      prev = curr.call(this, prev);
    }

    yield *prev;
  }
}

這裏是koa控制流的核心,其實代碼很簡單,可是須要細心分析下

1.while循環是逆序的,也就是說最後一個genfunc接收的參數是noop, 源碼是:

function *noop(){}

2.從倒數第二個genfunc開始,每個方法接受的參數都是緊挨它下一個genfunc的實例,也就是咱們在koa方法中引用的形參: next,這樣便能經過在方法中使用yield next來控制流轉過程。

3.compose方法返回的也是一個generator,這樣co便能迭代它,可是它每次產生的數據並非promise,而是咱們註冊的genfunc的實例,因此這裏它使用了yield *prev,用來轉移迭代對象,這樣每次迭代的就是咱們註冊是生成器了。

4.若是你看到這裏你看懂了,就會有個疑問:爲何在咱們註冊的生成器中,咱們使用的是yield next,而不是yield *next。其實你怎麼寫均可以,co內部對res.value作了類型判斷,若是是generator,它本身會遞歸調用co(generator),而co也會返回一個promise。(我寫的core方法沒有對此進行描述)

這裏說明下 yield generator 和yield *generator的區別:前者會將一個generator做爲返回值,後者則會將控制交給這個generator,並迭代它,詳細介紹能夠看 MDN-function *


寫到這裏,koa的實現方法和流轉控制基本就清楚了,這裏作一個總結:

1.在koa註冊的genfunc中,能夠經過yield next 來控制程序的流轉,若是不當心忘記了作這個事情,那麼經過上面的代碼能夠發現,程序會捨棄後面的generator,提早返回。

2.整個控制流實際像一個洋蔥,先進的必定會後出,後進入的必定會先出,可是若是咱們故意跳開某一步的next調用,那麼這個洋蔥就不會被從中間穿過,而是穿過部分外層,而後逆序在穿過這些外層,程序就執行完了,不會進入後面註冊的那些genfunc。

3.因爲上面的緣由,這裏有一個陷阱:若是你使用了相似router之類的組件,一般來講通常若是匹配了某個指定的path,這個router就會執行,而後它後面的genfunc會被捨棄,它會提早返回。而若是你要配置一些全局的處理,譬如壓縮html,關閉數據庫鏈接這類必須等到最後執行的操做,則必須在這些有可能會中斷控制流的方法以前註冊,而後經過yield next直接轉移給下一步便可,否則有可能會被提早返回不被執行。

打完收工,歡迎批評指正。

相關文章
相關標籤/搜索