浅析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直接转移给下一步便可,否则有可能会被提早返回不被执行。

打完收工,欢迎批评指正。

相关文章
相关标签/搜索