对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方法开始探索其中的机制。


 
 
  1. function co(generator){ 
  2.   let g = generator(); 
  3.   let next = function(data){ 
  4.       let result = g.next(data); 
  5.  
  6.       if(result.done){ 
  7.           return ; 
  8.       }; 
  9.  
  10.       if(result.value instanceof Promise){ 
  11.           result.value.then(function(d){ 
  12.               next(d); 
  13.           },function(err){ 
  14.               next(err); 
  15.           }); 
  16.       }else{ 
  17.           next(); 
  18.       }; 
  19.   }; 
  20.  
  21.   next(); 
  22. }; 

首先需要了解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 文件:


 
 
  1. /** 
  2.  * Use the given middleware 'fn'. 
  3.  * 
  4.  * @param {GeneratorFunction} fn 
  5.  * @return {Application} self 
  6.  * @api public 
  7.  */ 
  8.  
  9. app.use = function(fn){ 
  10.   if (!this.experimental) { 
  11.     // es7 async functions are not allowed, 
  12.     // so we have to make sure that 'fn' is a generator function 
  13.     assert(fn && 'GeneratorFunction' == fn.constructor.name, 'app.use() requires a generator function'); 
  14.   } 
  15.   debug('use %s', fn._name || fn.name || '-'); 
  16.   this.middleware.push(fn); 
  17.   return this; 
  18. }; 

显而易见, app.use() 方法就是将generator传入 this.middleware 数组中。其他部分的逻辑源码注释非常清晰,不再赘述。

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


 
 
  1. /** 
  2.  * Compose `middleware` returning 
  3.  * a fully valid middleware comprised 
  4.  * of all those which are passed. 
  5.  * 
  6.  * @param {Array} middleware 
  7.  * @return {Function} 
  8.  * @api public 
  9.  */ 
  10.  
  11. function compose(middleware){ 
  12.   return function *(next){ 
  13.     if (!next) next = noop(); 
  14.  
  15.     var i = middleware.length; 
  16.  
  17.     while (i--) { 
  18.       next = middleware[i].call(this, next); 
  19.     } 
  20.  
  21.     return yield *next; 
  22.   } 
  23. } 

其中最关键的就是 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

相关文章
|
4月前
|
存储 人工智能 运维
阿里云 OSS 向量 Bucket 正式商业化,提升 AI 应用效能
阿里云 OSS 向量 Bucket 将于 6月10日 GA,支持万亿级向量存储与语义检索,成本降低 95%。结合对象桶、表格桶,构建 AI Native 多模态统一存储底座,赋能 RAG、AI Agent 等场景。
845 0
|
11月前
|
自然语言处理 搜索推荐 算法
别再给我推明星八卦了!——大数据视角下,个性化新闻推荐的“人间真实”优化指南
别再给我推明星八卦了!——大数据视角下,个性化新闻推荐的“人间真实”优化指南
816 11
|
5月前
|
缓存 前端开发 NoSQL
办公Agent架构设计:如何让一个Agent同时服务销售、运营、人事部门?
本文讲述一个企业级多部门Agent从混乱到优雅的架构演进:直面意图冲突、权限隔离与知识打架三大难题,通过V1失败尝试、V2部门路由+上下文隔离、V3分层知识库(公共/部门/个人)三阶段迭代,最终实现单Agent安全、精准、高效服务销售、运营、人事等多部门。含真实避坑经验与落地案例。(240字)
427 4
|
7月前
|
人工智能 前端开发 Serverless
10 分钟部署 Qwen3!阿里云 FunctionAI 模板实测,成本低至 ¥0.5/小时
通义千问Qwen3正式开源8款混合推理模型,含2款MoE(如Qwen3-235B-A22B)和6款Dense模型(从0.6B到32B),支持119种语言、思考/非思考双模式,在代码、数学等基准测试中表现优异。依托阿里云函数计算FC与FunctionAI平台,提供vLLM/SGLang/Ollama等多种部署方式,开箱即用。
1468 30
|
5月前
|
人工智能 自然语言处理 监控
工业级 RAG 不可或缺 的 RAG 评估方案:2大核心指标、三大流程 落地优化,让RAG从Demo走向生产
工业级 RAG 不可或缺 的 RAG 评估方案:2大核心指标、三大流程 落地优化,让RAG从Demo走向生产
|
人工智能 搜索推荐 JavaScript
【Geo专家于磊】深度解析:Geo优化中的Schema标签,如何让你的内容在AI时代脱颖而出?
微笑老师详解Geo优化中Schema标签的写法,揭示如何通过结构化数据提升AI时代下的内容可见性。从选择类型、填写关键属性到JSON-LD格式应用与测试验证,全面掌握Geo优化核心技巧,助力本地商家在搜索结果中脱颖而出。(238字)
1259 4
|
运维 前端开发 数据处理
浅谈领域驱动在前端的应⽤
领域驱动是⼀种思想,不仅可以应⽤于软件开发,也没有绝对的开发规范,适合⾃⼰的业务和团队背景就好,我们不是为了应⽤⽽应⽤,⽽是为了解决问题。
|
搜索推荐 BI API
ZoomEye-python工具使用详解(一)
ZoomEye-python工具使用详解(一)
705 0

热门文章

最新文章