对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.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,代码只有短短几十行,但组合在一起却非常精巧奇妙,值得细细品味。
作者:佚名
来源:51CTO

低调大师中文资讯倾力打造互联网数据资讯、行业资源、电子商务、移动互联网、网络营销平台。
持续更新报道IT业界、互联网、市场资讯、驱动更新,是最及时权威的产业资讯及硬件资讯报道平台。
转载内容版权归作者及来源网站所有,本站原创内容转载请注明来源。
- 上一篇
高级恶意软件是如何逃避“僵化沙箱”的?
如今,用以对付高级恶意软件的沙箱技术已被恶意软件的作者利用。网络罪犯越来越多地使用这种技术来创造新技术来逃避这种防御。 沙箱规避并不是一种新现象,其开始于恶意软件开始认识到自己正在一个沙箱中,并且要“睡眠”到超时。但是安全分析工具在检测“睡眠”时已经更为高效,所以恶意软件的作者正在创造新策略,例如用来感染微软办公文档中的恶意软件变种。再如,有的恶意软件可以向内存写入近百亿次一个字节的随机数据。沙箱并不能够判定应用程序正在有意地拖延,因为它并没有真正地“睡眠”。此外,过多代码或垃圾代码迫使安全分析师花费更多的时间检查和分析恶意软件。 考虑到攻击者不断地更新其攻击技术,企业的恶意软件分析很有可能超越了传统的沙箱技术。企业在购买和部署沙箱技术时通常有三种典型的方法: 1. 作为一种独立的方案,对其它安全产品无依赖性。 2. 内建到基于网络的安全设备(如防火墙、IPS、UTM)中。 3. 内建到安全内容网关中,如Web或电子邮件网关。 虽然每种部署选择都有其自己的优点和缺点,但传统的沙箱技术一般都以同样的方式工作:析取恶意样本;在本地虚拟机中分析样本;生成报告。但其面临着类似的局限性:能够感知...
- 下一篇
2017 年软件开发人员需要面对的七个改变
2017年对开发者而言将是有趣的一年,因为这个行业将会发生一些重大的变化。从创建较新的应用程序到添加功能到现有的应用程序,以扩展应用程序跨越其当前的限制,编程将在2017年见证复兴。 人工智能玩得越来越high 人工智能将在2017年大扬其威。AI作为新的用户界面不再是一个片面的方法。我们需要理解,利用人工智能需要两个策略——一个包括开发人员编写的代码,另一个专注于接口如何收集来自源的数据——从而使AI更加智能。 必须知道的一点是虽然代码或内容块仍然是王牌,但是匿名化数据集同样重要。第一步始终是从框架、平台和编程语言收集信息,然后将其全部转移给开发人员,之后他们将为该作业选择最佳数据集。 2017年将会看到大多数公司拥抱人工智能——集合他们寻求员工的大量数据——主要用于预测长期的战略和发展。 利用理解自然语言的Chatbot驱动app Chatbots本质上是智能的,2017年将使大多数软件开发人员与他们一起工作。这里的想法是构造可以满足特定用户需求的特定应用。无论是检查Facebook上的航班还是使用像Cinemabox这样的流媒体服务,都会有一大批能够理解自然语言并做出相应回应的c...
相关文章
文章评论
共有0条评论来说两句吧...
文章二维码
点击排行
-
Docker使用Oracle官方镜像安装(12C,18C,19C)
- Springboot2将连接池hikari替换为druid,体验最强大的数据库连接池
- Docker快速安装Oracle11G,搭建oracle11g学习环境
- CentOS8编译安装MySQL8.0.19
- SpringBoot2配置默认Tomcat设置,开启更多高级功能
- MySQL8.0.19开启GTID主从同步CentOS8
- Jdk安装(Linux,MacOS,Windows),包含三大操作系统的最全安装
- CentOS7,8上快速安装Gitea,搭建Git服务器
- SpringBoot2编写第一个Controller,响应你的http请求并返回结果
推荐阅读
最新文章
- Windows10,CentOS7,CentOS8安装Nodejs环境
- Linux系统CentOS6、CentOS7手动修改IP地址
- 设置Eclipse缩进为4个空格,增强代码规范
- SpringBoot2配置默认Tomcat设置,开启更多高级功能
- Hadoop3单机部署,实现最简伪集群
- Springboot2将连接池hikari替换为druid,体验最强大的数据库连接池
- MySQL8.0.19开启GTID主从同步CentOS8
- Docker使用Oracle官方镜像安装(12C,18C,19C)
- CentOS7安装Docker,走上虚拟化容器引擎之路
- CentOS7编译安装Cmake3.16.3,解决mysql等软件编译问题