洋蔥圈這個概念起源於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