对Koa-middleware实现机制的深入分析

栏目: Node.js · 发布时间: 8年前

内容简介:对Koa-middleware实现机制的深入分析

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相关知识,接下来我们逐步分析这段代码:

1.我们首先定义一个参数为generator的co函数。

2.当传入generator后(即 app.use(function *(){...}) )定义 next 方法实现对generator(可以理解为状态机)的状态遍历,由于每次遍历器指向新的 yield ,返回结构如 {value:'Promise','done':'true/false'} 的值,当 done 的值为 false 时遍历状态完毕并返回,若为 true 则继续遍历。其中内部的 g.next(data) 可以将上一个 yield 的返回值传递给外部。

3.同时,若generator中含有多个 yield 且遍历未完成(即 result.valuePromise 对象 && result.done === false ), resolve() 所传递的数据可以在接下来 then() 方法中直接使用,即递归调用,直到 result.done === true 遍历结束并退出。

这里可能存在一个疑惑,在第一次调用 next() 方法时data为 undefined ,那是否会导致error产生呢?其实V8引擎在执行时,会自动忽略第一次调用 next() 时的参数,所以只有从第二次使用 next() 方法时参数才是有效的。

一言以蔽之,co实现了Promise递归调用generator的next方法。

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,代码只有短短几十行,但组合在一起却非常精巧奇妙,值得细细品味。


以上就是本文的全部内容,希望本文的内容对大家的学习或者工作能带来一定的帮助,也希望大家多多支持 码农网

查看所有标签

猜你喜欢:

本站部分资源来源于网络,本站转载出于传递更多信息之目的,版权归原作者或者来源机构所有,如转载稿涉及版权问题,请联系我们

Learn Python 3 the Hard Way

Learn Python 3 the Hard Way

Zed A. Shaw / Addison / 2017-7-7 / USD 30.74

You Will Learn Python 3! Zed Shaw has perfected the world’s best system for learning Python 3. Follow it and you will succeed—just like the millions of beginners Zed has taught to date! You bring t......一起来看看 《Learn Python 3 the Hard Way》 这本书的介绍吧!

RGB转16进制工具
RGB转16进制工具

RGB HEX 互转工具

图片转BASE64编码
图片转BASE64编码

在线图片转Base64编码工具