【原創(chuàng)】NodeJS中間件機制學(xué)習

理解NodeJS中間件機制核心代碼的實現(xiàn),加深對中間件機制的理解,有助于更好的使用和編寫中間件。

目錄

  • 中間件概念
  • 中間件機制核心實現(xiàn)
  • 中間件社區(qū)

中間件概念

在NodeJS中,中間件主要是指封裝所有Http請求細節(jié)處理的方法。一次Http請求通常包含很多工作,如記錄日志、ip過濾、查詢字符串、請求體解析、Cookie處理、權(quán)限驗證、參數(shù)驗證、異常處理等,但對于Web應(yīng)用而言,并不希望接觸到這么多細節(jié)性的處理,因此引入中間件來簡化和隔離這些基礎(chǔ)設(shè)施與業(yè)務(wù)邏輯之間的細節(jié),讓開發(fā)者能夠關(guān)注在業(yè)務(wù)的開發(fā)上,以達到提升開發(fā)效率的目的。

中間件的行為比較類似Java中過濾器的工作原理,就是在進入具體的業(yè)務(wù)處理之前,先讓過濾器處理。它的工作模型下圖所示。

中間件工作模型

中間件機制核心實現(xiàn)

中間件是從Http請求發(fā)起到響應(yīng)結(jié)束過程中的處理方法,通常需要對請求和響應(yīng)進行處理,因此一個基本的中間件的形式如下:

const middleware = (req, res, next) => {
  // TODO
  next()
}

以下通過兩種方式的中間件機制的實現(xiàn)來理解中間件是如何工作的。

方式一

如下定義三個簡單的中間件:

const middleware1 = (req, res, next) => {
  console.log('middleware1 start')
  next()
}

const middleware2 = (req, res, next) => {
  console.log('middleware2 start')
  next()
}

const middleware3 = (req, res, next) => {
  console.log('middleware3 start')
  next()
}

通過遞歸的形式,將后續(xù)中間件的執(zhí)行方法傳遞給當前中間件,在當前中間件執(zhí)行結(jié)束,通過調(diào)用next()方法執(zhí)行后續(xù)中間件的調(diào)用。

// 中間件數(shù)組
const middlewares = [middleware1, middleware2, middleware3]
function run (req, res) {
  const next = () => {
    // 獲取中間件數(shù)組中第一個中間件
    const middleware = middlewares.shift()
    if (middleware) {
      middleware(req, res, next)
    }
  }
  next()
}
run() // 模擬一次請求發(fā)起

執(zhí)行以上代碼,可以看到如下結(jié)果:

middleware1 start
middleware2 start
middleware3 start

如果中間件中有異步操作,需要在異步操作的流程結(jié)束后再調(diào)用next()方法,否則中間件不能按順序執(zhí)行。改寫middleware2中間件:

const middleware2 = (req, res, next) => {
  console.log('middleware2 start')
  new Promise(resolve => {
    setTimeout(() => resolve(), 1000)
  }).then(() => {
    next()
  })
}

執(zhí)行結(jié)果與之前一致,不過middleware3會在middleware2異步完成后執(zhí)行。

執(zhí)行結(jié)果

方式二

有些中間件不止需要在業(yè)務(wù)處理前執(zhí)行,還需要在業(yè)務(wù)處理后執(zhí)行,比如統(tǒng)計時間的日志中間件。在方式一情況下,無法在next()為異步操作時再將當前中間件的其他代碼作為回調(diào)執(zhí)行。因此可以將next()方法的后續(xù)操作封裝成一個Promise對象,中間件內(nèi)部就可以使用next.then()形式完成業(yè)務(wù)處理結(jié)束后的回調(diào)。改寫run()方法如下:

function run (req, res) {
  const next = () => {
    const middleware = middlewares.shift()
    if (middleware) {
      // 將middleware(req, res, next)包裝為Promise對象
      return Promise.resolve(middleware(req, res, next))
    }
  }
  next()
}

中間件的調(diào)用方式需改寫為:

const middleware1 = (req, res, next) => {
  console.log('middleware1 start')
  // 所有的中間件都應(yīng)返回一個Promise對象
  // Promise.resolve()方法接收中間件返回的Promise對象,供下層中間件異步控制
  return next().then(() => {
    console.log('middleware1 end')
  })
}

得益于async函數(shù)的自動異步流程控制,中間件也可以用如下方式來實現(xiàn):

// async函數(shù)自動返回Promise對象
const middleware2 = async (req, res, next) => {
  console.log('middleware2 start')
  await new Promise(resolve => {
    setTimeout(() => resolve(), 1000)
  })
  await next()
  console.log('middleware2 end')
}

const middleware3 = async (req, res, next) => {
  console.log('middleware3 start')
  await next()
  console.log('middleware3 end')
}

執(zhí)行結(jié)果如下:

執(zhí)行結(jié)果

以上描述了中間件機制中多個異步中間件的調(diào)用流程,實際中間件機制的實現(xiàn)還需要考慮異常處理、路由等。

express框架中,中間件的實現(xiàn)方式為方式一,并且全局中間件和內(nèi)置路由中間件中根據(jù)請求路徑定義的中間件共同作用,不過無法在業(yè)務(wù)處理結(jié)束后再調(diào)用當前中間件中的代碼。koa2框架中中間件的實現(xiàn)方式為方式二,將next()方法返回值封裝成一個Promise,便于后續(xù)中間件的異步流程控制,實現(xiàn)了koa2框架提出的洋蔥圈模型,即每一層中間件相當于一個球面,當貫穿整個模型時,實際上每一個球面會穿透兩次。

koa2中間件洋蔥圈模型

koa2框架的中間件機制實現(xiàn)得非常簡潔和優(yōu)雅,這里學(xué)習一下框架中組合多個中間件的核心代碼。

function compose (middleware) {
  if (!Array.isArray(middleware)) throw new TypeError('Middleware stack must be an array!')
  for (const fn of middleware) {
    if (typeof fn !== 'function') throw new TypeError('Middleware must be composed of functions!')
  }
  return function (context, next) {
    let index = -1
    return dispatch(0)
    function dispatch (i) {
      // index會在next()方法調(diào)用后累加,防止next()方法重復(fù)調(diào)用
      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 {
        // 核心代碼
        // 包裝next()方法返回值為Promise對象
        return Promise.resolve(fn(context, dispatch.bind(null, i + 1)));
      } catch (err) {
        // 遇到異常中斷后續(xù)中間件的調(diào)用
        return Promise.reject(err)
      }
    }
  }
}

中間件社區(qū)

在后續(xù)NodeJS學(xué)習和應(yīng)用中,建議使用koa2框架作為基礎(chǔ)框架,這里列出了一些使用比較多的中間件。

koa中間件列表地址:https://github.com/koajs/koa/wiki

中間件的具體使用方式還請小伙伴們自行查詢官方文檔。

總結(jié)

本文主要介紹了中間件的概念、為何引入中間件以及中間件機制的核心實現(xiàn)。中間件機制使得Web應(yīng)用具備良好的可擴展性和組合性。

在實現(xiàn)中間件時,單個中間件應(yīng)該足夠簡單,職責單一。由于每個請求都會調(diào)用中間件相關(guān)代碼,中間件的代碼應(yīng)該高效,必要的時候可以緩存重復(fù)獲取的數(shù)據(jù)。在對不同的路由使用中間件時,還應(yīng)該考慮到不同的中間件應(yīng)用到不同的路由上。

本文參考資源如下

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
【社區(qū)內(nèi)容提示】社區(qū)部分內(nèi)容疑似由AI輔助生成,瀏覽時請結(jié)合常識與多方信息審慎甄別。
平臺聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點,簡書系信息發(fā)布平臺,僅提供信息存儲服務(wù)。

相關(guān)閱讀更多精彩內(nèi)容

友情鏈接更多精彩內(nèi)容