一篇文章了解洋葱圈模型——看了就忘不掉的那种
洋葱圈这个概念起源于Koa,由于其灵活、易扩展,目前已经广泛的流传开来。例如umi-request的洋葱中间件机制,阿里内部的eaas封装(egg as a service),都用到了洋葱圈的概念。关于洋葱圈,其实论坛里也有很多的源码阅读文章,但大部分都直接讲源码,造成了一定的理解困难——包括我自己经常也是这样,难以把握住核心理念;因此,本文会采取提问题,定方案,然后带着方案回头去看源码的方式来剖析洋葱圈,希望大家能看过就懂,懂了就不会忘。
1. 洋葱圈模型
不管怎么聊,这张图还是要放一下的。可以看到,每个中间件都是一个洋葱圈。每次当有一个请求进入的时候,每个中间件都会被执行两次。例如下面的例子:
const Koa = require("koa")
const app = new Koa()
// 中间件A
app.use(async (ctx, next) => {
console.log("A1")
await next()
console.log("A2")
});
// 中间件B
app.use(async (ctx, next) => {
console.log("B1")
await next()
console.log("B2")
});
// 中间件C
app.use(async (ctx, next) => {
console.log("C1")
await next()
console.log("C2")
});
app.listen(3000);
// 输出
// A1 -> B1 -> C1 -> C2 -> B2 -> A2
2.核心理念
首先,我们来分析一下:
每个中间件都接收了一个next参数,在next函数运行之前的中间件代码会在一开始就执行,next函数之后的代码会在内部的中间件全部运行结束之后才执行。
要想达到上面洋葱圈的运行效果,我们需要做什么呢?
- 首先我们要知道当前中间件的数组集合
- 然后构建一个组合方法,对这些中间件按照洋葱的结构进行组合,并执行
我们带着这样的一个思路,再回头来看Koa是如何实现的:
this.middleware
是中间件集合的数组koa-compose
模块的compose方法用来构建执行顺序
完美!下面只需要具体分析一下它们分别做了什么就可以了
// middleware用来保存中间件
app.use = (fn) => {
this.middleware.push(fn)
return this
}
// compose组合函数来规定执行次序
function compose (middleware) {
// context:上下文,next:传入的接下来要运行的函数
return function (context, next) {
function dispatch (i) {
index = i
// 中间件
let fn = middleware[i]
if (!fn) return Promise.resolve()
try {
// 我们这边假设和上文中的例子一样,有A、B、C三个中间件
// 通过dispatch(0)发起了第一个中间件A的执行
// A中间件执行之后,next作为dispatch(1)会被执行
// 从而发起了下一个中间件B的执行,然后是中间件C被执行
// 所有的中间件都执行了一遍后,执行Promise.resolve()
// 最里面的中间件C的await next()运行结束,会继续执行console.log("C2")
// 整个中间件C的运行结束又触发了Promise.resolve
// 中间件B开始执行console.log("B2")
// 同理,中间件A执行console.log("A2")
return Promise.resolve(fn(context, () => {
return dispatch(i + 1)
}))
} catch (err) {
return Promise.reject(err)
}
}
return dispatch(0)
}
}
3. 整体回顾
Koa利用了在中间件中间传入next参数的方法,再结合middleware中间件数组和compose组合函数,构建了洋葱圈的中间件执行结构。这也是为什么洋葱圈中间件机制可以运行起来的原因。
洋葱圈的代码并不复杂,但是这种提出问题,带着解决思路去看代码的方式,希望能给大家一点启发。
4. 参考文章
1.《浅析koa的洋葱模型实现》
2.《Koa2 洋葱模型 —— compose 串联中间件的四种实现》
3.《umi-request 网络请求之路》
汤姆C
tomczhang的专栏,我会在这个专栏中撰写我的思考,工作中遇到的问题和苦恼,每周一篇
推荐阅读
项目经历准备篇——如何准备阿里巴巴P6/P7前端面试
在上次的校招文章之后,有很多同学问有没有社招相关的东西可以写一篇,现在它来了。比起校招,社招更加看重项目经历+项目经历反应的思考。本文针对的是想进入阿里的P6/P7同学,着重讲解了很多同学容易忽视的项目...
这是你的玩具车吗赞 30阅读 3.6k评论 5
手把手教你写一份优质的前端技术简历
不知不觉一年一度的秋招又来了,你收获了哪些大厂的面试邀约,又拿了多少offer呢?你身边是不是有挺多人技术比你差,但是却拿到了很多大厂的offer呢?其实,要想面试拿offer,首先要过得了简历那一关。如果一份简...
tonychen赞 152阅读 17.8k评论 5
正则表达式实例
收集在业务中经常使用的正则表达式实例,方便以后进行查找,减少工作量。常用正则表达式实例1. 校验基本日期格式 {代码...} {代码...} 2. 校验密码强度密码的强度必须是包含大小写字母和数字的组合,不能使用特殊...
寒青赞 56阅读 8.5k评论 11
JavaScript有用的代码片段和trick
平时工作过程中可以用到的实用代码集棉。判断对象否为空 {代码...} 浮点数取整 {代码...} 注意:前三种方法只适用于32个位整数,对于负数的处理上和Math.floor是不同的。 {代码...} 生成6位数字验证码 {代码...} ...
jenemy赞 49阅读 7.2k评论 12
从零搭建 Node.js 企业级 Web 服务器(十五):总结与展望
总结截止到本章 “从零搭建 Node.js 企业级 Web 服务器” 主题共计 16 章内容就更新完毕了,回顾第零章曾写道:搭建一个 Node.js 企业级 Web 服务器并非难事,只是必须做好几个关键事项这几件必须做好的关键事项就...
乌柏木赞 75阅读 7.1k评论 16
再也不学AJAX了!(二)使用AJAX ① XMLHttpRequest
「再也不学 AJAX 了」是一个以 AJAX 为主题的系列文章,希望读者通过阅读本系列文章,能够对 AJAX 技术有更加深入的认识和理解,从此能够再也不用专门学习 AJAX。本篇文章为该系列的第二篇,最近更新于 2023 年 1...
libinfs赞 42阅读 6.8k评论 12
CSS 绘制一只思否猫
欢迎关注我的公众号:前端侦探练习 CSS 有一个比较有趣的方式,就是发挥想象,绘制各式各样的图案,比如来绘制一只思否猫?思否猫,SegmentFault 思否的吉祥物,是一只独一无二、特立独行、热爱自由的(>^ω^<...
XboxYan赞 47阅读 3.3k评论 14
**粗体** _斜体_ [链接](http://example.com) `代码` - 列表 > 引用
。你还可以使用@
来通知其他用户。