对Koa-middleware实现机制的深入分析
Koa是对K的深基于Node.js的下一代web开发框架,相比Express更轻,实现源码只有几百行。机制与传统的入分中间件不同,在Koa 1.x中采用了generator实现中间件,对K的深这需要开发者熟悉ES6中的实现generator,Promise相关知识。机制
在Koa官方文档示例代码中,入分采用yield next为跳转信号,对K的深然后会逆序执行中间件剩下的实现代码逻辑。这其中的机制逻辑非常有趣,本文将对其进行深入的入分分析。
Section A:
Koa的对K的深中间件跑在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.value 是 Promise 对象 && 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 时,并且返回的 done 为 true 时,逆序执行。从此前用于保存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,代码只有短短几十行,但组合在一起却非常精巧奇妙,值得细细品味。