洋葱圈这个概念起源于Koa,因为其灵活、易扩展,目前已经普遍的流传开来。例如umi-request的洋葱中间件机制,阿里内部的eaas封装(egg as a service),都用到了洋葱圈的概念。关于洋葱圈,其实论坛里也有不少的源码阅读文章,但大部分都直接讲源码,形成了必定的理解困难——包括我本身常常也是这样,难以把握住核心理念;所以,本文会采起 提问题,定方案,而后带着方案回头去看源码的方式来剖析洋葱圈,但愿你们能看过就懂,懂了就不会忘。
无论怎么聊,这张图仍是要放一下的。能够看到,每一个中间件都是一个洋葱圈。每次当有一个请求进入的时候,每一个中间件都会被执行两次。例以下面的例子:segmentfault
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
首先,咱们来分析一下:数组
每一个中间件都接收了一个next参数,在next函数运行以前的中间件代码会在一开始就执行,next函数以后的代码会在内部的中间件所有运行结束以后才执行。网络
要想达到上面洋葱圈的运行效果,咱们须要作什么呢?app
- 首先咱们要知道当前中间件的数组集合
- 而后构建一个组合方法,对这些中间件按照洋葱的结构进行组合,并执行
咱们带着这样的一个思路,再回头来看Koa是如何实现的:koa
this.middleware
是中间件集合的数组koa-compose
模块的compose方法用来构建执行顺序
完美!下面只须要具体分析一下它们分别作了什么就能够了async
// 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) } }
Koa利用了在中间件中间传入next参数的方法,再结合middleware中间件数组和compose组合函数,构建了洋葱圈的中间件执行结构。这也是为何洋葱圈中间件机制能够运行起来的缘由。函数
洋葱圈的代码并不复杂,可是这种提出问题,带着解决思路去看代码的方式,但愿能给你们一点启发。ui
1.《浅析koa的洋葱模型实现》
2.《Koa2 洋葱模型 —— compose 串联中间件的四种实现》
3.《umi-request 网络请求之路》this