说说你对koa中洋葱模型的理解?

简介: 说说你对koa中洋葱模型的理解?

用过Koa的,肯定对 Middleware(中间件) 有所了解,那我们就用中间件从现象出发,理解洋葱模型

先自定义两个中间件:

logTime:打印时间戳

module.exports=function() {
returnasyncfunction(ctx, next) {
console.log("next前,打印时间戳:", newDate().getTime())
awaitnext()
console.log("next后,打印时间戳:", newDate().getTime())
}
}

logUrl:打印路由

module.exports=function() {
returnasyncfunction(ctx, next) {
console.log("next前,打印url:", ctx.url)
awaitnext()
console.log("next后,打印url:", ctx.url)
}
}
在 index.js 中use:
constKoa=require('koa')
constapp=newKoa()
constlogTime=require('./middleware/logTime')
constlogUrl=require('./middleware/logUrl')
// logTime
app.use(logTime())
// logUrl
app.use(logUrl())
// response
app.use(asyncctx=> {
ctx.body='Hello World'
})
app.listen(3000)

现在启动服务,然后我们随便访问一个路由,比如 /test,会发现这样一个现象:


两个现象:

  • 中间件的执行了两次
  • 执行顺序奇怪,以next函数为分界点:先use的中间件,next前的逻辑先执行,但next后的逻辑反而后执行

第一反应肯定就是:Koa为什么要这么设计?
正常不应该是中间件按顺序从开始到结束执行吗?

确实,如果说使用中间件的场景不存在前后依赖的情况,从头到尾按顺序链式调用完全没问题。但如果存在依赖的情况呢?如果只链式执行一次,怎么能保证前面的中间件能使用之后的中间件所添加的东西呢?
比如上面两个例子,我在 logUrl 的中间件里,对url进行了处理,加上了一个时间戳,然后我想在 logTime 的中间件里拿到这个时间戳并打印


如果只链式执行一次的话,显然无法实现


然后就是顺序的问题,为什么以next为分界线,先use的中间件 next 之后的逻辑反而后执行呢?


还是上面的例子,如果我们在logUrl 中间件里对url加上去的时间戳,是从数据库里取出来的,logTime 中间件肯定得等 logUrl 跑完了才能拿到对应时间戳。所以如果 logTime 之中的next完成的话,肯定是logUrl这个中间件已经跑完了


因此,上述现象1、2的原因就很清晰了


这样我们可以就确定中间件的执行流必定是如下的一个流程:

  • 外层中间件进行前期处理(next 前的逻辑)
  • 调用next,将控制流交给下个中间件,并await其完成,直到后面没有中间件或者没有next函数执行为止
  • 完成后一层层回溯执行各个中间件的后期处理(next 后的逻辑)

这就是洋葱模型

再放出那两张很经典的图:


Koa中,外层中间件称为上游,内层中间件称为下游,现在我们再回头看官网的描述:


先用我们自己的方式理解之后,官方描述就不会太晦涩了

知道了原因,我们肯定得手动来实现一下洋葱模型,加深理解

实现之前肯定得先看一波源码:

koa

koa-compose

来,一步步分析一波:

首先,先看看middleware在源码里是什么数据类型:

然后按流程看,肯定先进app的listen函数:https://blog.csdn.net/hyqhyqhyqq/article/details/null
创建服务的时候,传入了callback函数的返回值,去看看callback函数:


重点就是这里了,我们上面的分析说明想要实现洋葱模型,下面两点缺一不可:

  • 要把上下文ctx对象和下一个中间件next传给当前的中间件
  • 必须要等待下一个中间件执行完,再执行当前中间件的后续逻辑

而这就是compose函数所做的事情,来自于 koa-compose,这里先暂时不贴源码,有一说一很绕,强行看有点难受

所以,我们可以先按自己的思路来试试:

应该不需要解释吧,这样肯定会报错:


因为执行mw2的时候(也就是mw1里的next),并没有把ctx 和 mw3传给它


那么问题来了:我们怎么才能在调用mw1的next时,把ctx 和 mw2给这个next呢?


那我们肯定就需要对middleware数组里的每个元素重新包装一下了,用什么包装呢?


看个例子:


bind会将当时的参数保留下来,这正是我们所需要的,因此,加上一点小小的改动:


这个时候我们再跑一下代码:


这不就实现了吗?刚刚我留了一个坑就是没放 koa-compose 的源码,下面是源码:


红框的部分就是核心代码,大家可以自己看看,如果感觉很绕,可以对比我上面的例子先理解的,贴一下我简化版的代码:

constmiddleware= []
letmw1=asyncfunction (ctx, next) {
console.log("next前,第一个中间件")
awaitnext()
console.log("next后,第一个中间件")
}
letmw2=asyncfunction (ctx, next) {
console.log("next前,第二个中间件")
awaitnext()
console.log("next后,第二个中间件")
}
letmw3=asyncfunction (ctx, next) {
console.log("第三个中间件,没有next了")
}
functionuse(mw) {
middleware.push(mw)
}
use(mw1)
use(mw2)
use(mw3)
letfn=function (ctx) {
returndispatch(0)
functiondispatch(i) {
letcurrentMW=middleware[i]
if(!currentMW) {
return
}
returncurrentMW(ctx, dispatch.bind(null, i+1))
}
}
fn()

OK啦,这样就大工告成了

总结一下吧:一开始通过现象和场景反推,明白了什么时洋葱模型,以及为什么Koa需要使用洋葱模型,最后就是利用源码,简化实现了一下洋葱模型

相关文章
|
9月前
|
JavaScript 中间件
什么是koa洋葱模型?
什么是koa洋葱模型?
|
5天前
|
设计模式 中间件 开发者
Koa2 的洋葱模型是什么?它是如何实现的?
Koa2 的洋葱模型是什么?它是如何实现的?
48 0
|
9月前
|
中间件
说说你对koa中洋葱模型的理解?
说说你对koa中洋葱模型的理解?
77 0
|
5天前
|
前端开发 JavaScript 中间件
koa开发实践2:为koa项目添加路由模块
koa开发实践2:为koa项目添加路由模块
57 0
|
5天前
|
开发框架 JavaScript 前端开发
Koa2 的核心特点是什么?
Koa2 的核心特点是什么?
24 0
|
5天前
|
前端开发 中间件 索引
【源码共读】洋葱模型 koa-compose
【源码共读】洋葱模型 koa-compose
26 0
|
JavaScript 前端开发 中间件
前端网红框架的插件机制全梳理(axios、koa、redux、vuex)
前端中的库很多,开发这些库的作者会尽可能的覆盖到大家在业务中千奇百怪的需求,但是总有无法预料到的,所以优秀的库就需要提供一种机制,让开发者可以干预插件中间的一些环节,从而完成自己的一些需求。
|
JavaScript 前端开发 中间件
尝试理解 Koa、Redux middleware 的演进过程
最近开发 web 端,用的是 Egg node.js 框架,期间实现的一些功能例如:权限检测、操作日志上报等都是基于框架的 middleware 机制件完成的。虽然最后完成了功能,但其实对中间件真正的实现机制、运行时序还不能做到完全的理解。 Egg 是基于 Koa 实现的,Koa 的代码量非常少,加起来也就 1000 多行,涉及到中间件核心的部分,也就不到 100 行,如果有耐心可以直接读
70 0
|
前端开发 JavaScript 中间件
koa框架学习记录(8)
一个前端学习koa的简单记录
|
前端开发
koa框架学习记录(5)
一个前端学习koa的简单记录