对Koa-middleware完成机制的剖析

本文宣布在github.com/ssssyoki,迎接star,issues配合交换。

Koa是基于Node.js的下一代web开辟框架,比拟Express更轻,源码只要几百行。与传统的中间件差别,在Koa 1.x中采纳了generator完成中间件,这须要开辟者熟习ES6中的generator,Promise相干学问。

Koa官方文档示例代码中,采纳yield next为跳转信号,然后会逆序实行中间件剩下的代码逻辑。这个中的逻辑异常风趣,本文将对其举行扼要的剖析。

Section A:

Koa的中间件跑在co模块下,而co能够将异步“变成”同步,从而完成用同步的要领写异步代码,避免了Node.js大批的回调嵌套。如今我们从完成一个浅易的co要领最先探究个中的机制。

function co(generator){
  let g = generator();
  let next = function(data){
      let result = g.next(data);
      
      if(result.done){
          return ;
      };
      
      if(result.value instanceof Promise){
          result.value.then(function(d){
              next(d);
          },function(err){
              next(err);
          });
      }else{
          next();
      };
  };
  
  next();
};

起首须要相识generator相干学问,接下来我们剖析这段代码:

起首定义一个参数为generator的co函数,当传入generator后(即app.use(function *(){...}))定义next要领完成对generator(能够明白为状况机)的状况遍历,因为每次遍历器指向新的yield,返回构造如{value:'Promise','done':'true/false'}的值,当done的值为false时遍历状况终了并返回,若为true则继承遍历。个中内部的g.next(data)能够将上一个yield的返回值通报给外部。同时,若generator中含有多个yield且遍历未完成(即result.valuePromise对象 && result.done === false),resolve()所通报的数据能够在接下来then()要领中直接运用,即递归挪用,直到result.done === true遍历完毕并退出。

这里能够存在一个迷惑,在第一次挪用next()要领时data为undefined,那是不是会致使error发生呢?实在V8引擎在实行时,会自动疏忽第一次挪用next()时的参数,所以只要从第二次运用next()要领时参数才是有用的。

一言以蔽之,co完成了Promise递归挪用generator的next要领。

*仓库内co-example.js添加了测试代码,上述实例测试能够运转胜利。

Section B:

明白了co的运转道理后,再来明白middleware的机制就轻易多了。

middleware完成了所谓“逆序”实行,实在就是每次挪用use()要领时,将generator存入数组(记为s)中保留。在实行的时刻先定义一个实行索引(记为index)和跳转标记(记为turn,也就是yield next中的next),再定义一个保留generator函数对象的数组(记为gs)。然后猎取当前中间件generator,接着猎取该generator的函数对象,将函数对象放在gs数组内保留,再实行generator的next()要领。
实行最先后,依据返回的value举行差别的处置惩罚,如果是标记turn(即实行到了yield next),申明该跳到下一个中间件了,此时令index++,然后从数组g中猎取下一个中间件反复上一个中间件的实行流程。

当实行到的中间件没有yield时,而且返回的donetrue时,逆序实行。从此前用于保留generator函数对象的gs数组中掏出上一个generator对象,然后实行generator的next()要领,直到悉数完毕。

我们翻开Koa的application.js文件:


/**
 * Use the given middleware 'fn'.
 *
 * @param {GeneratorFunction} fn
 * @return {Application} self
 * @api public
 */

app.use = function(fn){
  if (!this.experimental) {
    // es7 async functions are not allowed,
    // so we have to make sure that 'fn' is a generator function
    assert(fn && 'GeneratorFunction' == fn.constructor.name, 'app.use() requires a generator function');
  }
  debug('use %s', fn._name || fn.name || '-');
  this.middleware.push(fn);
  return this;
};

不言而喻,app.use()要领就是将generator传入this.middleware数组中。其他部份的逻辑源码解释异常清楚,不再赘述。

我们再翻开Koa-compose模块的index.js文件:


/**
 * Compose `middleware` returning
 * a fully valid middleware comprised
 * of all those which are passed.
 *
 * @param {Array} middleware
 * @return {Function}
 * @api public
 */

function compose(middleware){
  return function *(next){
    if (!next) next = noop();

    var i = middleware.length;

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

    return yield *next;
  }
}

个中最症结的就是while语句。将之前app.use()传入并存储在middleware中的generator逆序掏出并实行,将每一个generator实行后的效果(即generator() === iterator)作为参数传入下一个(按数组的递次则为前一个)generator中,在末了一个generator(数组第一个)实行后得出的next变量(即第一个generator的iterator),实行yield *next(即实行第一个generator的iterator)将悉数generator像链表般串连起来。依据yield *的特征,yield *next将顺次实行一切套用的next(相似递归),从而构成所谓“正序实行再逆序实行”的流程。

从co到compose,代码只要短短几十行,但组合在一起却异常精致巧妙,值得细细品味。

    原文作者:ssssyoki
    原文地址: https://segmentfault.com/a/1190000007811920
    本文转自网络文章,转载此文章仅为分享知识,如有侵权,请联系博主进行删除。
点赞