怎么更好地理解中間件和洋蔥模型

這篇文章主要介紹“怎么更好地理解中間件和洋蔥模型”,在日常操作中,相信很多人在怎么更好地理解中間件和洋蔥模型問題上存在疑惑,小編查閱了各式資料,整理出簡單好用的操作方法,希望對大家解答”怎么更好地理解中間件和洋蔥模型”的疑惑有所幫助!接下來,請跟著小編一起來學習吧!

創(chuàng)新互聯(lián)公司專業(yè)為企業(yè)提供陽原網(wǎng)站建設(shè)、陽原做網(wǎng)站、陽原網(wǎng)站設(shè)計、陽原網(wǎng)站制作等企業(yè)網(wǎng)站建設(shè)、網(wǎng)頁設(shè)計與制作、陽原企業(yè)網(wǎng)站模板建站服務(wù),10多年陽原做網(wǎng)站經(jīng)驗,不只是建網(wǎng)站,更提供有價值的思路和整體網(wǎng)絡(luò)服務(wù)。

一、Koa 中間件

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

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

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

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

function wait(ms) {   return new Promise((resolve) => setTimeout(resolve, ms || 1)); }  const arr = []; const stack = [];  // type Middleware<T> = (context: T, next: Koa.Next) => any; stack.push(async (context, next) => {   arr.push(1);   await wait(1);   await next();   await wait(1);   arr.push(6); });  stack.push(async (context, next) => {   arr.push(2);   await wait(1);   await next();   await wait(1);   arr.push(5); });  stack.push(async (context, next) => {   arr.push(3);   await wait(1);   await next();   await wait(1);   arr.push(4); });  await compose(stack)({});

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

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

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

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

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

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

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

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

怎么更好地理解中間件和洋蔥模型

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

1.1 任務(wù)注冊

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

const Koa = require('koa'); const app = new Koa();  app.use(async (ctx, next) => {   const start = Date.now();   await next();   const ms = Date.now() - start;   console.log(`${ctx.method} ${ctx.url} - ${ms}ms`); });

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

// lib/application.js module.exports = class Application extends Emitter {     constructor(options) {     super();     // 省略部分代碼      this.middleware = [];   }     use(fn) {    if (typeof fn !== 'function') throw new TypeError('middleware must be a function!');    // 省略部分代碼     this.middleware.push(fn);    return this;   } }

由以上代碼可知,在 use 方法內(nèi)部會對 fn 參數(shù)進行類型校驗,當校驗通過時,會把 fn 指向的中間件保存到 middleware 數(shù)組中,同時還會返回  this 對象,從而支持鏈式調(diào)用。

1.2 任務(wù)編排

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

怎么更好地理解中間件和洋蔥模型

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


// 統(tǒng)計請求處理時長的中間件 app.use(async (ctx, next) => {   const start = Date.now();   await next();   const ms = Date.now() - start;   console.log(`${ctx.method} ${ctx.url} - ${ms}ms`); });

怎么更好地理解中間件和洋蔥模型

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

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

/**  * Compose `middleware` returning  * a fully valid middleware comprised  * of all those which are passed.  *  * @param {Array} middleware  * @return {Function}  * @api public  */ function compose(middleware) {   // 省略部分代碼   return function (context, next) {     // last called middleware #     let index = -1;     return dispatch(0);     function dispatch(i) {       if (i <= index)         return Promise.reject(new Error("next() called multiple times"));       index = i;       let fn = middleware[i];       if (i === middleware.length) fn = next;       if (!fn) return Promise.resolve();       try {         return Promise.resolve(fn(context, dispatch.bind(null, i + 1)));       } catch (err) {         return Promise.reject(err);       }     }   }; }

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

1.3.1 dispatch(0)

怎么更好地理解中間件和洋蔥模型

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

1.3.2 dispatch(1)

怎么更好地理解中間件和洋蔥模型

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

1.3.3 dispatch(2)

怎么更好地理解中間件和洋蔥模型

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

1.3.4 dispatch(3)

怎么更好地理解中間件和洋蔥模型

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

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

const Koa = require('koa'); const app = new Koa();  // 響應 app.use(ctx => {   ctx.body = '大家好,我是阿寶哥'; });  app.listen(3000);

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

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

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

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

// lib/application.js const compose = require('koa-compose');  module.exports = class Application extends Emitter {     callback() {     const fn = compose(this.middleware);     if (!this.listenerCount('error')) this.on('error', this.onerror);      const handleRequest = (req, res) => {       const ctx = this.createContext(req, res);       return this.handleRequest(ctx, fn);     };     return handleRequest;   }

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

module.exports = class Application extends Emitter {     handleRequest(ctx, fnMiddleware) {     const res = ctx.res;     res.statusCode = 404;     const onerror = err => ctx.onerror(err);     const handleResponse = () => respond(ctx);     onFinished(res, onerror);     return fnMiddleware(ctx).then(handleResponse).catch(onerror);   } }

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

二、洋蔥模型2.1 洋蔥模型簡介

怎么更好地理解中間件和洋蔥模型

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

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

2.2 洋蔥模型應用

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

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

怎么更好地理解中間件和洋蔥模型

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

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

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

// response async function respond(ctx, next) {   await next();   if ("/" != ctx.url) return;   ctx.body = "Hello World"; }

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

到此,關(guān)于“怎么更好地理解中間件和洋蔥模型”的學習就結(jié)束了,希望能夠解決大家的疑惑。理論與實踐的搭配能更好的幫助大家學習,快去試試吧!若想繼續(xù)學習更多相關(guān)知識,請繼續(xù)關(guān)注創(chuàng)新互聯(lián)網(wǎng)站,小編會繼續(xù)努力為大家?guī)砀鄬嵱玫奈恼拢?/p>

當前名稱:怎么更好地理解中間件和洋蔥模型
URL標題:http://muchs.cn/article28/jsocjp.html

成都網(wǎng)站建設(shè)公司_創(chuàng)新互聯(lián),為您提供動態(tài)網(wǎng)站、ChatGPTApp開發(fā)、營銷型網(wǎng)站建設(shè)、關(guān)鍵詞優(yōu)化企業(yè)網(wǎng)站制作

廣告

聲明:本網(wǎng)站發(fā)布的內(nèi)容(圖片、視頻和文字)以用戶投稿、用戶轉(zhuǎn)載內(nèi)容為主,如果涉及侵權(quán)請盡快告知,我們將會在第一時間刪除。文章觀點不代表本網(wǎng)站立場,如需處理請聯(lián)系客服。電話:028-86922220;郵箱:631063699@qq.com。內(nèi)容未經(jīng)允許不得轉(zhuǎn)載,或轉(zhuǎn)載時需注明來源: 創(chuàng)新互聯(lián)

成都網(wǎng)站建設(shè)