深入理解 Koa 中间件之 “ 洋葱模型 ”
欢迎关注我的公众号『 前端我废了 』,查看更多文章!!!
前言
我们知道创建一个 Koa 应用主要分三步:
const Koa = require('koa');
// 1. 创建一个 Koa 实例
const app = new Koa();// 2,加载多个中间件
app.use(/*中间件*/);
// ... 其他中间件// 3. 指定服务器端口,创建一个 http 服务器
app.listen(3000);
那么中间件的执行顺序是怎样的呢?执行顺序是如何生成的呢?这篇文章就让我们来一探究竟。
koa 中间件的执行顺序
如下示例代码,例如使用中间件 x-response-time
,logger
,response
const Koa = require('Koa');
const app = new Koa();// 中间件1 x-response-time
app.use(async (ctx, next) => {const start = Date.now();await next();const ms = Date.now() - start;ctx.set('X-Response-Time', `${ms}ms`);
});// 中间件2 logger
app.use(async (ctx, next) => {const start = Date.now();await next();const ms = Date.now() - start;console.log(`${ctx.method} ${ctx.url} - ${ms}`);
});// 中间件3 response
app.use(async ctx => {ctx.body = 'Hello World';
});app.listen(3000);
上面代码中间件的执行顺序如下动图
以上中间件的执行顺序,我们会常听到一个词叫做 “洋葱模型”(如下图),何为“洋葱模型“?洋葱内的每一层表示一个独立的中间件,用于实现不同的功能,比如日志记录,异常处理等。每次请求都会从左侧最外层开始,一层层经过中间件,当执行到最里层的中间件之后,接着从最里层的中间件开始逐层返回。因此对于每层的中间件来说,在一个 请求和响应 周期中,都有两个时机点来添加不同的处理逻辑。是不是有点像 DOM 事件流的事件捕获阶段(从外到里)和事件冒泡阶段(从里到外)。
上面中间件的执行顺序是怎么生成的呢?从 Koa 源码入手,从 Koa 源码的 `package.json 中 main 字段得知入口文件指向的是 lib/application.js 文件,详解一下创建一个 koa 应用执行过程:
const app = new Koa()
;我们new Koa()
其实就是创建类Application
的实例;app.use(/*中间件*/)
;当我们调用app.use(function)
加载中间件时,use 函数内部将中间件函数都保存到了 middleware 数组里面;app.listen(/* 端口 */)
,内部使用http.createServer()
创建一个 http 服务器,并调用 this.callback() 的返回值作为参数;callback 函数内部调用 compose 函数(即 koa-compose 中间件),就是造就中间件执行顺序的"幕后黑手"。
下面我们逐行解析 koa-compose
源码,看看是如何生成 “洋葱模型”的。
koa-compose 源码逐行详解
koa-compose 源码(index.js 文件)内部导出的就是 compose 函数,我们除了看源码,也可以结合对应测试用例(test.js 文件)来理解。
/*** compose 函数接收一个中间件数组,返回一个中间件函数* @param {Array} middleware* @return {Function}*/
function compose (middleware) {// 若传入的参数 middleware 不是数组,则抛错if (!Array.isArray(middleware)) throw new TypeError('Middleware stack must be an array!')// 若 middleware 数组项不是函数,则抛错for (const fn of middleware) {if (typeof fn !== 'function') throw new TypeError('Middleware must be composed of functions!')}/*** 返回一个中间件函数,第一参数为一个请求的上下文对象;第二个参数 next 函数为调用下一个中间件的函数*/return function (context, next) {// 当前中间件在 middleware 数组中的索引位置let index = -1// 返回 dispatch(0) 结果return dispatch(0)// 该函数目的递归调用中间件,生成中间件嵌套调用结构function dispatch (i) {// 每个中间件函数内部只能调用一次 next 函数,否则抛错// 测试用例 should throw if next() is called multiple times if (i <= index) return Promise.reject(new Error('next() called multiple times'))// 记录索引index = i// 取出当前中间件函数let fn = middleware[i]// 若当前索引值等于中间件数组的长度,即 middleware 数组的中间件都处理完了,则 fn 赋值为参数 nextif (i === middleware.length) fn = next// 若 fn 不存在,则 resolve 掉,结束if (!fn) return Promise.resolve()try {// 返回 Promise,执行中间件函数 fn,这里将下一个中间件函数执行器作为第二个参数传入当前中间件,目的是将下一个中间件函数的执行权交由当前中间件,在其内部手动调用;// 这里利用 bind 函数来实现,bind 函数执行后会返回一个新的函数,并不会立即执行,什么时候执行呢?也就是当前中间件 fn 函数里的 await next() 执行时,此时这个 next 函数也就是现在 fn 函数传入的第二个参数 dispatch.bind(null, (i + 1)的返回值return Promise.resolve(fn(context, dispatch.bind(null, i + 1)))} catch (err) {return Promise.reject(err)}}}
}
假如有三个中间件分别为 fn1
、fn2
、fn3
,那么在 Koa 内部经过中间件 koa-compose
组合后,将会生成如下的嵌套结构
// compose 函数返回值也是一个中间件函数
const fn = compose([fn1, fn2, fn3])
// compose 返回的函数 fn 内部嵌套结构,简略代码
function(context){return Promise.resolve(fn1(context, function next(){return Promise.resolve(fn2(context, function next(){return Promise.resolve(fn3(context, function next(){return Promise.resolve();}))}))}));
};
以上就是生成中间件执行顺序的代码,核心就是 dispatch 函数,通过递归生成中间件嵌套调用结构,将下一个中间件的控制权通过函数参数的形式传递给当前中间件,以此类推,生成类似洋葱模型结构的执行顺序。
扩展:Koa 与 Express 对比
express 拥有路由、模板等框架常见功能,Koa 不含任何中间件,Koa 可被视为 node.js 的
http
模块的抽象,Express 则是 node.js 的应用程序框架。中间件实现机制;express 基于 Callback,koa 基于 Promise
错误处理;express 对错捕获处理起来很不友好,每一个回调都拥有一个新的调用栈,因此你没法对一个 callback 做 try catch 捕获,你需要在 Callback 里做错误捕获,然后一层一层向外传递。
响应机制;express在调用 res.send 方法后就立即响应了,而koa则是在所有中间件调用完成之后,在最外层中间件进行响应。
引用一段其他网友总结的 xpress 和 koa 中间件机制的不同:
其实中间件执行逻辑没有什么特别的不同,都是依赖函数调用栈的执行顺序,抬杠一点讲都可以叫做洋葱模型。Koa 依靠 async/await(generator + co)让异步操作可以变成同步写法,更好理解。最关键的不是这些中间的执行顺序,而是响应的时机,Express 使用
res.end()
是立即返回,这样想要做出些响应前的操作变得比较麻烦;而 Koa 是在所有中间件中使用ctx.body
设置响应数据,但是并不立即响应,而是在所有中间件执行结束后,再调用res.end(ctx.body)
进行响应,这样就为响应前的操作预留了空间,所以是请求与响应都在最外层,中间件处理是一层层进行,所以被理解成洋葱模型,个人拙见。
总结
牛逼!!!
参考
如何更好地理解中间件和洋葱模型
多维度分析 Express、Koa 之间的区别
Koa与 express 的中间件机制揭秘
Egg.js 与 Koa
Koa-vs-express
Talk about koa’s onion model
深入理解 Koa 中间件之 “ 洋葱模型 ”相关推荐
- 如何更好地理解中间件和洋葱模型
相信用过 Koa.Redux 或 Express 的小伙伴对中间件都不会陌生,特别是在学习 Koa 的过程中,还会接触到 "洋葱模型". 本文阿宝哥将跟大家一起来学习 Koa 的中 ...
- web前端技术分享:koa中间件是如何实现的?
在前端开发过程中我们可能会使用到koa中间件,但很多同学却不知道它是如何实现的,下面小千就来给大家介绍一下这个koa中间件(洋葱模型). 一.问题分析 async await是promise的语法糖, ...
- 洋葱模型php,理解Koa洋葱模型
中间件特性 | | | middleware 1 | | | | +-----------------------------------------------------------+ | | | ...
- 说说你对koa中洋葱模型的理解?
用过Koa的,肯定对 Middleware(中间件) 有所了解,那我们就用中间件从现象出发,理解洋葱模型 先自定义两个中间件: logTime:打印时间戳 module.exports=functio ...
- 你需要掌握的 Koa 洋葱模型和中间件
大家好,我是前端西瓜哥. Koa 是一个 nodejs 框架,经常用于写 web 后端服务.它是 Express 框架的原班人马开发的新一代 web 框架,使用了 async / await 来优雅处 ...
- 聊一聊KOA的洋葱模型
Koa 是一个新的 web 框架,由 Express 幕后的原班人马打造, 致力于成为 web 应用和 API 开发领域中的一个更小.更富有表现力.更健壮的基石. 通过利用 async 函数,Koa ...
- KOA 初探 洋葱模型
KOA 官网链接 当然,如果你有express的基础就最好了,毕竟是原班人马开发 常用的指令 npm install koa npm install nodemon touch app.js node ...
- 【Koa】为什么一定要保证是洋葱模型呢?
为什么一定要保证是洋葱模型呢? const Koa = require('koa');const app = new Koa(); /*聊一聊为什么我们通常需要在koa的中间件加上async,如果这个 ...
- 洋葱模型php,聊一聊KOA的洋葱模型
Koa 是一个新的 web 框架,由 Express 幕后的原班人马打造, 致力于成为 web 应用和 API 开发领域中的一个更小.更富有表现力.更健壮的基石. 通过利用 async 函数,Koa ...
最新文章
- mysql集群安装配置
- 前端-----盒子模型
- OpenHarmony的多内核
- 转jmeter --JDBC请求
- python中的栈结构_Python可以实现栈的结构吗
- logstash 获取多个kafka_logstash 配置详解
- .NET 5.0 RC1 发布,离正式版发布仅剩两个版本,与 netty 相比更具竞争力
- python之路--day10-闭包函数
- python画三维图-Python+matplotlib绘制三维图形5个精选案例
- IdentityServer4支持的授权类型以及组合
- html页面跳转方式 + 跳转传参
- [转]Spring 注解总结
- python贝叶斯网络预测模型_概率图模型之:贝叶斯网络
- AD15如何在PCB界面锁定选中元件
- 第十届泰迪杯数据挖掘B题电力系统负荷预测分析
- 高德导航java_通过拼接实现高清地图的下载-高德-java实现
- 编译安装wpa_supplicant
- 诗歌三 不积跬步,无以至千里
- Matlab系列教程_基础知识_绘图(一)
- c语言指针学多久,C语言指针难学吗?
热门文章
- 嵌入式Linux中间件,高可用性(HA)和嵌入式管理中间件:Enea Element详解
- centos7配置 console口_7.5. Configuring the Linux Console
- 功耗大好还是小好_热设计功耗高好还是低好 - 卡饭网
- 中日电脑相关词汇(超详版)
- 如果你没读懂《骇客帝国》
- 关于成功人士成功秘诀的乱弹琴
- imvu为什么显示无法连接服务器,IMVU服务器错误怎么办 服务器无法连接解决办法...
- 香港机房中的BGP线路具有哪些优势
- Linux Shell脚本语句执行失败,后续语句继续执行的问题
- 《那些年入上百万的人是如何做到的》读后感