三言两语说透koa的洋葱模型

news2024/12/25 11:33:11

Koa是一个非常轻量化的Node.js web应用框架,其洋葱圈模型是它独特的设计理念和核心实现机制之一。本文将详细介绍Koa的洋葱圈模型背后的设计思想,以及它是如何实现的。

洋葱圈模型设计思想

Koa的洋葱圈模型主要是受函数式编程中的compose思想启发而来的。Compose函数可以将需要顺序执行的多个函数复合起来,后一个函数将前一个函数的执行结果作为参数。这种函数嵌套是一种函数式编程模式。

Koa借鉴了这个思想,其中的中间件(middleware)就相当于compose中的函数。请求到来时会经过一个中间件栈,每个中间件会顺序执行,并把执行结果传给下一个中间件。这就像洋葱一样,一层层剥开。

这样的洋葱圈模型设计有以下几点好处:

  • 更好地封装和复用代码逻辑,每个中间件只需要关注自己的功能;
  • 更清晰的程序逻辑,通过中间件的嵌套可以表明代码的执行顺序;
  • 更好的错误处理,每个中间件可以选择捕获错误或将错误传递给外层;
  • 更高的扩展性,可以很容易地在中间件栈中添加或删除中间件。

洋葱圈模型实现机制

Koa的洋葱圈模型主要是通过Generator函数和Koa Context对象来实现的。

Generator函数

Generator是ES6中新增的一种异步编程解决方案。简单来说,Generator函数可以像正常函数那样被调用,但其执行体可以暂停在某个位置,待到外部重新唤起它的时候再继续往后执行。这使其非常适合表示异步操作。

// koa中使用generator函数表示中间件执行链
function *logger(next){
  console.log('outer');
  yield next;
  console.log('inner');
}

function *main(){
  yield logger();
}

var gen = main();
gen.next(); // outer
gen.next(); // inner

Koa使用Generator函数来表示洋葱圈模型中的中间件执行链。外层不断调用next重新执行Generator函数体,Generator函数再按顺序yield内层中间件异步操作。这样就可以很优雅地表示中间件的异步串行执行过程。

Koa Context对象

Koa Context封装了请求上下文,作为所有中间件共享的对象,它保证了中间件之间可以通过Context对象传递信息。具体而言,Context对象在所有中间件间共享以下功能:

  • ctx.request:请求对象
  • ctx.response:响应对象
  • ctx.state:推荐的命名空间,用于中间件间共享数据
  • ctx.throw:手动触发错误
  • ctx.app:应用实例引用
// Context对象示例
ctx = {
  request: {...}, 
  response: {...},
  state: {},
  throw: function(){...},
  app: {...}
}

// 中间件通过ctx对象传递信息
async function middleware1(ctx){
  ctx.response.body = 'hello';
}

async function middleware2(ctx){
  let body = ctx.response.body; 
  //...
}

每次请求上下文创建后,这个Context实例会在所有中间件间传递,中间件可以通过它写入响应,传递数据等。

中间件执行流程

当请求到达Koa应用时,会创建一个Context实例,然后按顺序执行中间件栈:

  1. 最内层中间件首先执行,可以操作Context进行一些初始化工作;
  2. 用yield将执行权转交给下一个中间件;
  3. 下一个中间件执行,并再次yield交还执行权;
  4. 当最后一个中间件执行完毕后,倒序执行中间件的剩余逻辑;
  5. 每个中间件都可以读取之前中间件写入Context的状态;
  6. 最外层获得Context并响应请求。
// 示意中间件执行流程
app.use(async function(ctx, next){
  // 最内层执行
  ctx.message = 'hello';

  await next();
  
  // 最内层剩余逻辑  
});

app.use(async function(ctx, next){
  // 第二层执行
  
  await next();

  // 第二层剩余逻辑
  console.log(ctx.message); 
});

// 最外层获得ctx并响应

这就是洋葱圈模型核心流程,通过Generator函数和Context对象实现了优雅的异步中间件机制。

完整解析

Koa中间件是一个Generator函数,可以通过yield关键字来调用下一个中间件。例如:

const Koa = require('koa');
const app = new Koa();

app.use(async (ctx, next) => {
  console.log('中间件1开始');
  
  await next();
  
  console.log('中间件1结束');
});

app.use(async (ctx, next) => {
  console.log('中间件2');

  await next();

  console.log('中间件2结束');  
});

app.use(async ctx => {
  console.log('中间件3')
});

app.listen(3000);

在代码中,可以看到Koa注册中间件是通过app.use实现的。所有中间件的回调函数中,await next()前面的逻辑是按照中间件注册的顺序从上往下执行的,而await next()后面的逻辑是按照中间件注册的顺序从下往上执行的。

执行流程如下:

  1. 收到请求,进入第一个中间件
  2. 第一个中间件打印日志,调用next进入第二个中间件
  3. 第二个中间件打印日志,调用next进入第三个中间件
  4. 第三个中间件打印日志,并结束请求
  5. control返回第二个中间件,打印结束日志
  6. control返回第一个中间件,打印结束日志
  7. 请求结束

这样每个中间件都可以控制请求前和请求后,形成洋葱圈模型。

中间件的实现原理

Koa通过compose函数来组合中间件,实现洋葱圈模型。compose接收一个中间件数组作为参数,执行数组中的中间件,返回一个可以执行所有中间件的函数。

compose函数的实现源码如下:

function compose (middleware) {

  return function (context, next) {
    // last called middleware #
    let index = -1
    return dispatch(0)
    function dispatch (i) {
      if (i <= index) return Promise.reject(new Error('next() called multiple times'))
      index = i
      let fn = middleware[i]
      if (i === middleware.length) fn = next
      if (!fn) return Promise.resolve()
      try {
        return Promise.resolve(fn(context, dispatch.bind(null, i + 1)));
      } catch (err) {
        return Promise.reject(err)
      }
    }
  }
}

这里利用了函数递归的机制。dispatch函数接收当前中间件的索引i,如果i大于中间件数组长度,则执行next函数。如果i小于中间件数组长度,则取出对应索引的中间件函数执行。

中间件的执行过程

执行中间件函数的时候,递归调用dispatch,同时将索引+1,表示执行下一个中间件。

这样通过递归不断调用dispatch函数,就可以依次执行每个中间件,实现洋葱圈模型。

所以Koa的洋葱圈模型实现得非常简洁优雅,这也是Koa作为新一代Node框架,相比Express更优秀的设计。

洋葱圈模型的优势

提高中间件的复用性

洋葱模型让每个中间件都可以控制请求前和请求后,这样中间件可以根据需要完成各种额外的功能,不会相互干扰,提高了中间件的复用性。

使代码结构更清晰

洋葱模型层层嵌套,执行流程一目了然,代码阅读性好,结构清晰。不会像其他模型那样回调多层嵌套,代码难以维护。

异步编程更简单

洋葱模型通过async/await,使异步代码可以以同步的方式编写,没有回调函数,代码逻辑更清晰。

错误处理更友好

每个中间件都可以捕获自己的错误,并且不会影响其他中间件的执行,这样对错误处理更加友好。

方便Debug

通过洋葱模型可以清楚看到每个中间件的进入和离开,方便Debug。

便于扩展

可以随意在洋葱圈的任意层增加或删除中间件,结构灵活,便于扩展。

总结

总体来说,洋葱模型使中间件更容易编写、维护和扩展,这也是Koa等新框架选择它的主要原因。它的嵌套结构和异步编程支持,使Koa的中间件机制更优雅和高效。

一川说

觉得文章不错的读者,不妨点个关注,收藏起来上班摸鱼的时候品尝。

欢迎关注笔者公众号「宇宙一码平川」,助你技术路上一码平川。

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.coloradmin.cn/o/825896.html

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

MybatisPlus存在 sql 注入漏洞(CVE-2023-25330)解决办法

首先我们了解下这个漏洞是什么&#xff1f; MyBatis-Plus TenantPlugin 是 MyBatis-Plus 的一个为多租户场景而设计的插件&#xff0c;可以在 SQL 中自动添加租户 ID 来实现数据隔离功能。 MyBatis-Plus TenantPlugin 3.5.3.1及之前版本由于 TenantHandler#getTenantId 方法在…

openEuler22.03安装 filebeat启动失败

报错详情 runtime/cgo: pthread_create failed: Operation not permitted runtime/cgo: pthread_create failed: Operation not permitted SIGABRT: abort PC=0x7faeea51af1f m=8 sigcode=18446744073709551610goroutine 0 [idle]: runtime: unknown pc 0x7faeea51af1f stack:…

pycharm出现python test运行报错(pytest模式)

pycharm出现python test运行报错 一、python test 执行代码报错二、删除运行配置三、修改pycharm默认配置为 unittests四、成功&#xff01; 一、python test 执行代码报错 二、删除运行配置 三、修改pycharm默认配置为 unittests 四、成功&#xff01;

【EI/SCOPUS会议征稿】第三届电气工程与计算机技术国际学术会议(ICEECT 2023)

第三届电气工程与计算机技术国际学术会议 2023 3rd International Conference on Electrical Engineering and Computer Technology 往届均已完成EI、SCOPUS检索 继ICEECT2021、ICEECT2022顺利举办&#xff0c;往届所录用论文均已完成出版及EI核心检索。第三届电气工程与计算…

招投标系统简介 招投标系统源码 java招投标系统 招投标系统功能设计 tbms

&#xfeff;功能模块&#xff1a; 待办消息&#xff0c;招标公告&#xff0c;中标公告&#xff0c;信息发布 描述&#xff1a; 全过程数字化采购管理&#xff0c;打造从供应商管理到采购招投标、采购合同、采购执行的全过程数字化管理。通供应商门户具备内外协同的能力&…

Vue引入与Vue拦截原理

1. vue引入 第一种方法&#xff1a;在线引入 <script src"https://cdn.jsdelivr.net/npm/vue/dist/vue.js"></script> 第二种方法&#xff1a;本地引入 2. Vue拦截原理——例题 el用于绑定id&#xff0c;data用于定义数据如下例题 <!DOCTYPE html&…

初学者入门进阶吉他推荐,卡马F1和VEAZEN费森VZ90系列怎么选?详细对比评测,哪一款更符合你的首选!

在新手入门单板吉他圈里&#xff0c;KEPMA卡马F1系列和VEAZEN费森VZ90系列是国内品牌一直都很热销的吉他型号&#xff0c;那么&#xff0c;今天就给大家做一个对比&#xff0c;新手们可以通过各方面评测分析&#xff0c;理性的看看哪把琴适合你买。希望对不知道如何选一把合适吉…

05|Oracle学习(UNIQUE约束)

1. UNIQUE约束介绍 也叫&#xff1a;唯一键约束&#xff0c;用于限定数据表中字段值的唯一性。 1.1 UNIQUE和primary key区别&#xff1a; 主键/联合主键每张表中只有一个。UNIQUE约束可以在一张表中&#xff0c;多个字段中存在。例如&#xff1a;学生的电话、身份证号都是…

三言两语说透柯里化和反柯里化

JavaScript中的柯里化(Currying)和反柯里化(Uncurrying)是两种很有用的技术&#xff0c;可以帮助我们写出更加优雅、泛用的函数。本文将首先介绍柯里化的概念、实现原理和应用场景&#xff0c;然后介绍反柯里化的概念、实现原理和应用场景&#xff0c;通过大量的代码示例帮助读…

[SSM]SpringMVC详解

目录 一、SpringMVC简介 1.1什么是MVC 1.2什么是SpringMVC 1.3SpringMVC优点 1.4SpringMVC优化的方向 1.5SpringMVC执行的流程 1.6基于注解的SpringMVC程序 二、SpringMVC注解式开发 2.1RequestMapping定义请求规则 2.1.1指定模块名称 2.1.2对请求提交方式的定义 2…

好用的低代码开发平台是什么样的?

一、好用的低代码开发平台是什么样的&#xff1f; 从企业角度来说&#xff0c;优化流程&#xff0c;提升企业运行效率&#xff1b;节省成本&#xff0c;提高企业效益&#xff1b;维护方便&#xff0c;即改即用&#xff1b;一键升级&#xff0c;方便实用&#xff1b; 从开发者角…

JVM | 从类加载到JVM内存结构

引言 我在上篇文章&#xff1a;JVM | 基于类加载的一次完全实践 中为你讲解如何请“建筑工人”来做一些定制化的工作。但是&#xff0c;大型的Java应用程序时&#xff0c;材料&#xff08;类&#xff09;何止数万&#xff0c;我们直接堆放在工地上&#xff08;JVM&#xff09;…

企业如何有效保护文件传输的安全性

文件传输是现代商业世界中每个企业日常操作的必需品。但是&#xff0c;传统的文件传输方式&#xff0c;如电子邮件和网络共享&#xff0c;并不总是安全可靠。黑客攻击、网络钓鱼和数据泄露等风险时刻存在。因此&#xff0c;企业需要采取措施保障文件传输的安全性。本文将介绍如…

Shell脚本学习-case条件语句

case条件语句相当于多分支的if/elif/else条件语句&#xff0c;但是它更规范工整。常被应用于实现系统服务启动脚本等企业应用场景中。 语法结构&#xff1a; case "变量" in值1)指令1...;;值2)指令2...;;*)指令3... esac 说明&#xff1a; 1&#xff09;case语句…

从 GPU 到 ChatGPT,一文带你理清GPU/CPU/AI/NLP/GPT之间的千丝万缕【建议收藏】

目录 硬件 GPU 什么是 GPU&#xff1f; GPU 是如何工作的&#xff1f; GPU 和 CPU 的区别 GPU 厂商 海外头部 GPU 厂商&#xff1a; 国内 GPU 厂商&#xff1a; nvidia 的产品矩阵 AI 什么是人工智能 (Artificial Intelligence-AI)&#xff1f; 人工智能细分领域 …

手把手教你写代码——基于控制台的通讯录管理系统(多人)(代码详细注释)

写在前面 本文章适合刚开始学习java的同学&#xff0c;不适合已参与java开发的人群&#xff01;本项目源代码已绑定资源中可免费获取&#xff01;如果对你有帮助请 栏目介绍 本栏目专为入门java学习者设计的一些简单的入门项目 功能介绍 本项目为简单的基于控制台的通讯录管理系…

音乐节《迷笛音乐节》游玩感

上周&#xff0c;去了烟台&#xff0c;参加音乐节&#xff0c;以前从未参加过&#xff0c;所以趁着本周六周日双休的时候&#xff0c;去游玩了一次。&#xff08;1&#xff09;一种新奇体验 对于自己来说&#xff0c;参加音乐节还是一种新奇的体验的&#xff0c;也是疫情放开了…

【MyBatis】初学MyBatis

目录 MyBatis 是什么&#xff1f;MyBatis框架搭建1.添加MyBatis框架2.设置MyBatis配置数据库的相关链接信息xml 保存路径和命名格式 根据MyBatis写法完成数据库的操作MyBatis插件MyBatis传递参数查询${} 和 #{} 有什么区别&#xff1f;SQL注入问题 MyBatis like查询MyBatis多表…

Lombok,一个神奇的存在

1、概述 Lombok主要用于在编译POJO类源文件时通过注解的方式自动为该类生成构造方法、getter/setter、equals、hashcode、toString等方法&#xff0c;有效地简化了POJO类代码&#xff0c;提高了软件的开发速度。 2、安装 a、启动IntelliJ IDEA—>点击CtrlAltS快捷键&…

【LeetCode】链表反转

题目 题目&#xff1a;给定单链表头节点&#xff0c;将单链表的链接顺序反转过来 例&#xff1a; 输入&#xff1a;1->2->3->4->5 输出&#xff1a;5->4->3->2->1 要求&#xff1a;按照两种方式实现 解决办法 方式一&#xff1a;&#xff08;直接迭…