成人免费xxxxx在线视频软件_久久精品久久久_亚洲国产精品久久久_天天色天天色_亚洲人成一区_欧美一级欧美三级在线观看

如何更好地理解中間件和洋蔥模型

開(kāi)發(fā) 前端
本文阿寶哥將跟大家一起來(lái)學(xué)習(xí) Koa 的中間件,不過(guò)這里阿寶哥不打算一開(kāi)始就亮出廣為人知的 “洋蔥模型圖”,而是先來(lái)介紹一下 Koa 中的中間件是什么?

[[349804]]

 相信用過(guò) Koa、Redux 或 Express 的小伙伴對(duì)中間件都不會(huì)陌生,特別是在學(xué)習(xí) Koa 的過(guò)程中,還會(huì)接觸到 “洋蔥模型”。

本文阿寶哥將跟大家一起來(lái)學(xué)習(xí) Koa 的中間件,不過(guò)這里阿寶哥不打算一開(kāi)始就亮出廣為人知的 “洋蔥模型圖”,而是先來(lái)介紹一下 Koa 中的中間件是什么?

一、Koa 中間件

在 @types/koa-compose 包下的 index.d.ts 頭文件中我們找到了中間件類型的定義:

  1. // @types/koa-compose/index.d.ts 
  2. declare namespace compose { 
  3.   type Middleware<T> = (context: T, next: Koa.Next) => any
  4.   type ComposedMiddleware<T> = (context: T, next?: Koa.Next) => Promise<void>; 
  5.    
  6. // @types/koa/index.d.ts => Koa.Next 
  7. type Next = () => Promise<any>; 

通過(guò)觀察 Middleware 類型的定義,我們可以知道在 Koa 中,中間件就是普通的函數(shù),該函數(shù)接收兩個(gè)參數(shù):context 和 next。其中 context 表示上下文對(duì)象,而 next 表示一個(gè)調(diào)用后返回 Promise 對(duì)象的函數(shù)對(duì)象。

了解完 Koa 的中間件是什么之后,我們來(lái)介紹 Koa 中間件的核心,即 compose 函數(shù):

  1. function wait(ms) { 
  2.   return new Promise((resolve) => setTimeout(resolve, ms || 1)); 
  3.  
  4. const arr = []; 
  5. const stack = []; 
  6.  
  7. // type Middleware<T> = (context: T, next: Koa.Next) => any
  8. stack.push(async (context, next) => { 
  9.   arr.push(1); 
  10.   await wait(1); 
  11.   await next(); 
  12.   await wait(1); 
  13.   arr.push(6); 
  14. }); 
  15.  
  16. stack.push(async (context, next) => { 
  17.   arr.push(2); 
  18.   await wait(1); 
  19.   await next(); 
  20.   await wait(1); 
  21.   arr.push(5); 
  22. }); 
  23.  
  24. stack.push(async (context, next) => { 
  25.   arr.push(3); 
  26.   await wait(1); 
  27.   await next(); 
  28.   await wait(1); 
  29.   arr.push(4); 
  30. }); 
  31.  
  32. await compose(stack)({}); 

對(duì)于以上的代碼,我們希望執(zhí)行完 compose(stack)({}) 語(yǔ)句之后,數(shù)組 arr 的值為 [1, 2, 3, 4, 5, 6]。這里我們先不關(guān)心 compose 函數(shù)是如何實(shí)現(xiàn)的。我們來(lái)分析一下,如果要求數(shù)組 arr 輸出期望的結(jié)果,上述 3 個(gè)中間件的執(zhí)行流程:

1.開(kāi)始執(zhí)行第 1 個(gè)中間件,往 arr 數(shù)組壓入 1,此時(shí) arr 數(shù)組的值為 [1],接下去等待 1 毫秒。為了保證 arr 數(shù)組的第 1 項(xiàng)為 2,我們需要在調(diào)用 next 函數(shù)之后,開(kāi)始執(zhí)行第 2 個(gè)中間件。

2.開(kāi)始執(zhí)行第 2 個(gè)中間件,往 arr 數(shù)組壓入 2,此時(shí) arr 數(shù)組的值為 [1, 2],繼續(xù)等待 1 毫秒。為了保證 arr 數(shù)組的第 2 項(xiàng)為 3,我們也需要在調(diào)用 next 函數(shù)之后,開(kāi)始執(zhí)行第 3 個(gè)中間件。

3.開(kāi)始執(zhí)行第 3 個(gè)中間件,往 arr 數(shù)組壓入 3,此時(shí) arr 數(shù)組的值為 [1, 2, 3],繼續(xù)等待 1 毫秒。為了保證 arr 數(shù)組的第 3 項(xiàng)為 4,我們要求在調(diào)用第 3 個(gè)中間的 next 函數(shù)之后,要能夠繼續(xù)往下執(zhí)行。

4.當(dāng)?shù)?3 個(gè)中間件執(zhí)行完成后,此時(shí) arr 數(shù)組的值為 [1, 2, 3, 4]。因此為了保證 arr 數(shù)組的第 4 項(xiàng)為 5,我們就需要在第 3 個(gè)中間件執(zhí)行完成后,返回第 2 個(gè)中間件 next 函數(shù)之后語(yǔ)句開(kāi)始執(zhí)行。

5.當(dāng)?shù)?2 個(gè)中間件執(zhí)行完成后,此時(shí) arr 數(shù)組的值為 [1, 2, 3, 4, 5]。同樣,為了保證 arr 數(shù)組的第 5 項(xiàng)為 6,我們就需要在第 2 個(gè)中間件執(zhí)行完成后,返回第 1 個(gè)中間件 next函數(shù)之后語(yǔ)句開(kāi)始執(zhí)行。

6.當(dāng)?shù)?1 個(gè)中間件執(zhí)行完成后,此時(shí) arr 數(shù)組的值為 [1, 2, 3, 4, 5, 6]。

為了更直觀地理解上述的執(zhí)行流程,我們可以把每個(gè)中間件當(dāng)做 1 個(gè)大任務(wù),然后在以 next 函數(shù)為分界點(diǎn),在把每個(gè)大任務(wù)拆解為 3 個(gè) beforeNext、next 和 afterNext 3 個(gè)小任務(wù)。

 

 

 

 

在上圖中,我們從中間件一的 beforeNext 任務(wù)開(kāi)始執(zhí)行,然后按照紫色箭頭的執(zhí)行步驟完成中間件的任務(wù)調(diào)度。在 77.9K 的 Axios 項(xiàng)目有哪些值得借鑒的地方 這篇文章中,阿寶哥從 任務(wù)注冊(cè)、任務(wù)編排和任務(wù)調(diào)度 3 個(gè)方面去分析 Axios 攔截器的實(shí)現(xiàn)。同樣,阿寶哥將從上述 3 個(gè)方面來(lái)分析 Koa 中間件機(jī)制。

1.1 任務(wù)注冊(cè)

在 Koa 中,我們創(chuàng)建 Koa 應(yīng)用程序?qū)ο笾?,就可以通過(guò)調(diào)用該對(duì)象的 use 方法來(lái)注冊(cè)中間件:

  1. const Koa = require('koa'); 
  2. const app = new Koa(); 
  3.  
  4. app.use(async (ctx, next) => { 
  5.   const start = Date.now(); 
  6.   await next(); 
  7.   const ms = Date.now() - start; 
  8.   console.log(`${ctx.method} ${ctx.url} - ${ms}ms`); 
  9. }); 

其實(shí) use 方法的實(shí)現(xiàn)很簡(jiǎn)單,在 lib/application.js 文件中,我們找到了它的定義:

  1. // lib/application.js 
  2. module.exports = class Application extends Emitter {   
  3.   constructor(options) { 
  4.     super(); 
  5.     // 省略部分代碼  
  6.     this.middleware = []; 
  7.   } 
  8.    
  9.  use(fn) { 
  10.    if (typeof fn !== 'function') throw new TypeError('middleware must be a function!'); 
  11.    // 省略部分代碼  
  12.    this.middleware.push(fn); 
  13.    return this; 
  14.   } 

由以上代碼可知,在 use 方法內(nèi)部會(huì)對(duì) fn 參數(shù)進(jìn)行類型校驗(yàn),當(dāng)校驗(yàn)通過(guò)時(shí),會(huì)把 fn 指向的中間件保存到 middleware 數(shù)組中,同時(shí)還會(huì)返回 this 對(duì)象,從而支持鏈?zhǔn)秸{(diào)用。

1.2 任務(wù)編排

在 77.9K 的 Axios 項(xiàng)目有哪些值得借鑒的地方 這篇文章中,阿寶哥參考 Axios 攔截器的設(shè)計(jì)模型,抽出以下通用的任務(wù)處理模型:

 

 

 

 

在該通用模型中,阿寶哥是通過(guò)把前置處理器和后置處理器分別放到 CoreWork 核心任務(wù)的前后來(lái)完成任務(wù)編排。而對(duì)于 Koa 的中間件機(jī)制來(lái)說(shuō),它是通過(guò)把前置處理器和后置處理器分別放到 await next() 語(yǔ)句的前后來(lái)完成任務(wù)編排


 

  1. // 統(tǒng)計(jì)請(qǐng)求處理時(shí)長(zhǎng)的中間件 
  2. app.use(async (ctx, next) => { 
  3.   const start = Date.now(); 
  4.   await next(); 
  5.   const ms = Date.now() - start; 
  6.   console.log(`${ctx.method} ${ctx.url} - ${ms}ms`); 
  7. }); 

 

1.3 任務(wù)調(diào)度

通過(guò)前面的分析,我們已經(jīng)知道了,使用 app.use 方法注冊(cè)的中間件會(huì)被保存到內(nèi)部的 middleware 數(shù)組中。要完成任務(wù)調(diào)度,我們就需要不斷地從 middleware 數(shù)組中取出中間件來(lái)執(zhí)行。中間件的調(diào)度算法被封裝到 koa-compose 包下的 compose 函數(shù)中,該函數(shù)的具體實(shí)現(xiàn)如下:

  1. /** 
  2.  * Compose `middleware` returning 
  3.  * a fully valid middleware comprised 
  4.  * of all those which are passed. 
  5.  * 
  6.  * @param {Array} middleware 
  7.  * @return {Function
  8.  * @api public 
  9.  */ 
  10. function compose(middleware) { 
  11.   // 省略部分代碼 
  12.   return function (context, next) { 
  13.     // last called middleware # 
  14.     let index = -1; 
  15.     return dispatch(0); 
  16.     function dispatch(i) { 
  17.       if (i <= index
  18.         return Promise.reject(new Error("next() called multiple times")); 
  19.       index = i; 
  20.       let fn = middleware[i]; 
  21.       if (i === middleware.length) fn = next
  22.       if (!fn) return Promise.resolve(); 
  23.       try { 
  24.         return Promise.resolve(fn(context, dispatch.bind(null, i + 1))); 
  25.       } catch (err) { 
  26.         return Promise.reject(err); 
  27.       } 
  28.     } 
  29.   }; 

compose 函數(shù)接收一個(gè)參數(shù),該參數(shù)的類型是數(shù)組,調(diào)用該函數(shù)之后會(huì)返回一個(gè)新的函數(shù)。接下來(lái)我們將以前面的例子為例,來(lái)分析一下 await compose(stack)({}); 語(yǔ)句的執(zhí)行過(guò)程。

1.3.1 dispatch(0)

 

 

 

 

由上圖可知,當(dāng)在第一個(gè)中間件內(nèi)部調(diào)用 next 函數(shù),其實(shí)就是繼續(xù)調(diào)用 dispatch 函數(shù),此時(shí)參數(shù) i 的值為 1。

1.3.2 dispatch(1)

 

 

 

 

由上圖可知,當(dāng)在第二個(gè)中間件內(nèi)部調(diào)用 next 函數(shù),仍然是調(diào)用 dispatch 函數(shù),此時(shí)參數(shù) i 的值為 2。

1.3.3 dispatch(2)

 

 

 

 

由上圖可知,當(dāng)在第三個(gè)中間件內(nèi)部調(diào)用 next 函數(shù),仍然是調(diào)用 dispatch 函數(shù),此時(shí)參數(shù) i 的值為 3。

1.3.4 dispatch(3)

 

 

 

 

由上圖可知,當(dāng) middleware 數(shù)組中的中間件都開(kāi)始執(zhí)行之后,如果調(diào)度時(shí)未顯式地設(shè)置 next 參數(shù)的值,則會(huì)開(kāi)始返回 next 函數(shù)之后的語(yǔ)句繼續(xù)往下執(zhí)行。當(dāng)?shù)谌齻€(gè)中間件執(zhí)行完成后,就會(huì)返回第二中間件 next 函數(shù)之后的語(yǔ)句繼續(xù)往下執(zhí)行,直到所有中間件中定義的語(yǔ)句都執(zhí)行完成。

分析完 compose 函數(shù)的實(shí)現(xiàn)代碼,我們來(lái)看一下 Koa 內(nèi)部如何利用 compose 函數(shù)來(lái)處理已注冊(cè)的中間件。

  1. const Koa = require('koa'); 
  2. const app = new Koa(); 
  3.  
  4. // 響應(yīng) 
  5. app.use(ctx => { 
  6.   ctx.body = '大家好,我是阿寶哥'
  7. }); 
  8.  
  9. app.listen(3000); 

利用以上的代碼,我就可以快速啟動(dòng)一個(gè)服務(wù)器。其中 use 方法我們前面已經(jīng)分析過(guò)了,所以接下來(lái)我們來(lái)分析 listen 方法,該方法的實(shí)現(xiàn)如下所示:

  1. // lib/application.js 
  2. module.exports = class Application extends Emitter {   
  3.   listen(...args) { 
  4.     debug('listen'); 
  5.     const server = http.createServer(this.callback()); 
  6.     return server.listen(...args); 
  7.   } 

很明顯在 listen 方法內(nèi)部,會(huì)先通過(guò)調(diào)用 Node.js 內(nèi)置 HTTP 模塊的 createServer 方法來(lái)創(chuàng)建服務(wù)器,然后開(kāi)始監(jiān)聽(tīng)指定的端口,即開(kāi)始等待客戶端的連接。

另外,在調(diào)用 http.createServer 方法創(chuàng)建 HTTP 服務(wù)器時(shí),我們傳入的參數(shù)是 this.callback(),該方法的具體實(shí)現(xiàn)如下所示:

  1. // lib/application.js 
  2. const compose = require('koa-compose'); 
  3.  
  4. module.exports = class Application extends Emitter {   
  5.   callback() { 
  6.     const fn = compose(this.middleware); 
  7.     if (!this.listenerCount('error')) this.on('error', this.onerror); 
  8.  
  9.     const handleRequest = (req, res) => { 
  10.       const ctx = this.createContext(req, res); 
  11.       return this.handleRequest(ctx, fn); 
  12.     }; 
  13.     return handleRequest; 
  14.   } 

在 callback 方法內(nèi)部,我們終于見(jiàn)到了久違的 compose 方法。當(dāng)調(diào)用 callback 方法之后,會(huì)返回 handleRequest 函數(shù)對(duì)象用來(lái)處理 HTTP 請(qǐng)求。每當(dāng) Koa 服務(wù)器接收到一個(gè)客戶端請(qǐng)求時(shí),都會(huì)調(diào)用 handleRequest 方法,在該方法會(huì)先創(chuàng)建新的 Context 對(duì)象,然后在執(zhí)行已注冊(cè)的中間件來(lái)處理已接收的 HTTP 請(qǐng)求:

  1. module.exports = class Application extends Emitter {   
  2.   handleRequest(ctx, fnMiddleware) { 
  3.     const res = ctx.res; 
  4.     res.statusCode = 404; 
  5.     const onerror = err => ctx.onerror(err); 
  6.     const handleResponse = () => respond(ctx); 
  7.     onFinished(res, onerror); 
  8.     return fnMiddleware(ctx).then(handleResponse).catch(onerror); 
  9.   } 

好的,Koa 中間件的內(nèi)容已經(jīng)基本介紹完了,對(duì) Koa 內(nèi)核感興趣的小伙伴,可以自行研究一下。接下來(lái)我們來(lái)介紹洋蔥模型及其應(yīng)用。

二、洋蔥模型2.1 洋蔥模型簡(jiǎn)介

 

 

 

 

(圖片來(lái)源:https://eggjs.org/en/intro/egg-and-koa.html)

在上圖中,洋蔥內(nèi)的每一層都表示一個(gè)獨(dú)立的中間件,用于實(shí)現(xiàn)不同的功能,比如異常處理、緩存處理等。每次請(qǐng)求都會(huì)從左側(cè)開(kāi)始一層層地經(jīng)過(guò)每層的中間件,當(dāng)進(jìn)入到最里層的中間件之后,就會(huì)從最里層的中間件開(kāi)始逐層返回。因此對(duì)于每層的中間件來(lái)說(shuō),在一個(gè) 請(qǐng)求和響應(yīng) 周期中,都有兩個(gè)時(shí)機(jī)點(diǎn)來(lái)添加不同的處理邏輯。

2.2 洋蔥模型應(yīng)用

除了在 Koa 中應(yīng)用了洋蔥模型之外,該模型還被廣泛地應(yīng)用在 Github 上一些不錯(cuò)的項(xiàng)目中,比如 koa-router 和阿里巴巴的 midway、umi-request 等項(xiàng)目中。

介紹完 Koa 的中間件和洋蔥模型,阿寶哥根據(jù)自己的理解,抽出以下通用的任務(wù)處理模型:

 

 

 

 

上圖中所述的中間件,一般是與業(yè)務(wù)無(wú)關(guān)的通用功能代碼,比如用于設(shè)置響應(yīng)時(shí)間的中間件:

  1. // x-response-time 
  2. async function responseTime(ctx, next) { 
  3.   const start = new Date(); 
  4.   await next(); 
  5.   const ms = new Date() - start; 
  6.   ctx.set("X-Response-Time", ms + "ms"); 

其實(shí),對(duì)于每個(gè)中間件來(lái)說(shuō),前置處理器和后置處理器都是可選的。比如以下中間件用于設(shè)置統(tǒng)一的響應(yīng)內(nèi)容:

  1. // response 
  2. async function respond(ctx, next) { 
  3.   await next(); 
  4.   if ("/" != ctx.url) return
  5.   ctx.body = "Hello World"

盡管以上介紹的兩個(gè)中間件都比較簡(jiǎn)單,但你也可以根據(jù)自己的需求來(lái)實(shí)現(xiàn)復(fù)雜的邏輯。Koa 的內(nèi)核很輕量,麻雀雖小五臟俱全。它通過(guò)提供了優(yōu)雅的中間件機(jī)制,讓開(kāi)發(fā)者可以靈活地?cái)U(kuò)展 Web 服務(wù)器的功能,這種設(shè)計(jì)思想值得我們學(xué)習(xí)與借鑒。

好的,這次就先介紹到這里,后面有機(jī)會(huì)的話,阿寶哥在單獨(dú)介紹一下 Redux 或 Express 的中間件機(jī)制。

 

責(zé)任編輯:姜華 來(lái)源: 全棧修仙之路
相關(guān)推薦

2022-10-25 08:01:17

洋蔥模型Koa

2021-06-15 10:01:02

應(yīng)用系統(tǒng)軟件

2020-08-19 08:39:05

中間件前端設(shè)計(jì)模式

2011-05-24 15:10:48

2021-02-11 08:21:02

中間件開(kāi)發(fā)CRUD

2022-03-18 06:32:43

遞歸Python算法

2021-08-07 07:23:08

Webpack中間件模型

2011-12-30 10:31:38

云計(jì)算

2022-07-14 08:17:59

中間件微服務(wù)開(kāi)發(fā)

2018-02-01 10:19:22

中間件服務(wù)器系統(tǒng)

2018-07-29 12:27:30

云中間件云計(jì)算API

2016-11-11 21:00:46

中間件

2021-06-29 09:34:00

洋蔥模型中間件

2014-06-05 14:41:20

金蝶中間件

2022-11-18 07:54:02

Go中間件項(xiàng)目

2021-07-19 07:55:24

Redux中間件原理

2023-10-18 07:32:27

中間件技術(shù)HTTP請(qǐng)求

2012-11-30 10:21:46

移動(dòng)中間件

2023-06-29 10:10:06

Rocket MQ消息中間件

2023-10-24 07:50:18

消息中間件MQ
點(diǎn)贊
收藏

51CTO技術(shù)棧公眾號(hào)

主站蜘蛛池模板: 91色视频在线观看 | 欧美天堂| 日韩在线精品视频 | 欧美三级三级三级爽爽爽 | 日韩一二区 | 天堂网色| 九九久久免费视频 | 小视频你懂得 | 欧美寡妇偷汉性猛交 | 成年人网站免费视频 | 美女爽到呻吟久久久久 | 国产精品视频免费观看 | 国产成人免费在线观看 | 欧美精品一区二区三区四区 在线 | 日韩视频在线免费观看 | 久久精品毛片 | 亚洲激情在线观看 | 在线一区二区三区 | 国产高清精品网站 | 国产在线一级片 | 久久精品无码一区二区三区 | 国产精品久久久久久久久久东京 | 婷婷色婷婷 | 九九九久久国产免费 | 国产成人久久精品 | 日韩在线免费播放 | 日韩在线播放一区 | 国产精品观看 | 国产二区三区 | 亚洲超碰在线观看 | 久久免费电影 | 久久久91 | 国产中文字幕在线 | 在线观看成人免费视频 | 伊人亚洲 | 91精品久久久久久久久中文字幕 | 日本黄色影片在线观看 | 中文字幕在线免费视频 | 看av网址 | 成人高潮片免费视频欧美 | 99re在线播放 |