亚洲香蕉成人av网站在线观看_欧美精品成人91久久久久久久_久久久久久久久久久亚洲_热久久视久久精品18亚洲精品_国产精自产拍久久久久久_亚洲色图国产精品_91精品国产网站_中文字幕欧美日韩精品_国产精品久久久久久亚洲调教_国产精品久久一区_性夜试看影院91社区_97在线观看视频国产_68精品久久久久久欧美_欧美精品在线观看_国产精品一区二区久久精品_欧美老女人bb

首頁 > 開發 > JS > 正文

淺談redux, koa, express 中間件實現對比解析

2024-05-06 16:51:17
字體:
來源:轉載
供稿:網友

如果你有 express ,koa, redux 的使用經驗,就會發現他們都有 中間件(middlewares)的概念,中間件 是一種攔截器的思想,用于在某個特定的輸入輸出之間添加一些額外處理,同時不影響原有操作。

最開始接觸 中間件是在服務端使用 express 和 koa 的時候,后來從服務端延伸到前端,看到其在redux的設計中也得到的極大的發揮。中間件的設計思想也為許多框架帶來了靈活而強大的擴展性。

本文主要對比redux, koa, express 的中間件實現,為了更直觀,我會抽取出三者中間件相關的核心代碼,精簡化,寫出模擬示例。示例會保持 express, koa,redux 的整體結構,盡量保持和源碼一致,所以本文也會稍帶講解下express, koa, redux 的整體結構和關鍵實現:

示例源碼地址, 可以一邊看源碼,一邊讀文章,歡迎star!

本文適合對express ,koa ,redux 都有一定了解和使用經驗的開發者閱讀

服務端的中間件
express 和 koa 的中間件是用于處理 http 請求和響應的,但是二者的設計思路確不盡相同。大部分人了解的express和koa的中間件差異在于:

  • express采用“尾遞歸”方式,中間件一個接一個的順序執行, 習慣于將response響應寫在最后一個中間件中;
  • 而koa的中間件支持 generator, 執行順序是“洋蔥圈”模型。

所謂的“洋蔥圈”模型:

redux,koa,express,中間件

不過實際上,express 的中間件也可以形成“洋蔥圈”模型,在 next 調用后寫的代碼同樣會執行到,不過express中一般不會這么做,因為 express的response一般在最后一個中間件,那么其它中間件 next() 后的代碼已經影響不到最終響應結果了;

express

首先看一下 express 的實現:

入口

// express.jsvar proto = require('./application');var mixin = require('merge-descriptors');exports = module.exports = createApplication;function createApplication() {  // app 同時是一個方法,作為http.createServer的處理函數 var app = function(req, res, next) {   app.handle(req, res, next) }  mixin(app, proto, false); return app}

這里其實很簡單,就是一個 createApplication 方法用于創建 express 實例,要注意返回值 app 既是實例對象,上面掛載了很多方法,同時它本身也是一個方法,作為 http.createServer的處理函數, 具體代碼在 application.js 中:

// application.jsvar http = require('http');var flatten = require('array-flatten');var app = exports = module.exports = {}app.listen = function listen() { var server = http.createServer(this) return server.listen.apply(server, arguments)}

這里 app.listen 調用 nodejs 的http.createServer 創建web服務,可以看到這里 var server = http.createServer(this) 其中 this 即 app 本身, 然后真正的處理程序即 app.handle;

中間件處理

express 本質上就是一個中間件管理器,當進入到 app.handle 的時候就是對中間件進行執行的時候,所以,最關鍵的兩個函數就是:

  • app.handle 尾遞歸調用中間件處理 req 和 res
  • app.use 添加中間件

全局維護一個stack數組用來存儲所有中間件,app.use 的實現就很簡單了,可以就是一行代碼 ``

// app.useapp.use = function(fn) { this.stack.push(fn)}

express 的真正實現當然不會這么簡單,它內置實現了路由功能,其中有 router, route, layer 三個關鍵的類,有了 router 就要對 path 進行分流,stack 中保存的是 layer實例,app.use 方法實際調用的是 router 實例的 use 方法, 有興趣的可以自行去閱讀。

app.handle 即對 stack 數組進行處理

app.handle = function(req, res, callback) { var stack = this.stack; var idx = 0; function next(err) { if (idx >= stack.length) { callback('err')  return; } var mid; while(idx < stack.length) { mid = stack[idx++]; mid(req, res, next); } } next()}

這里就是所謂的"尾遞歸調用",next 方法不斷的取出stack中的“中間件”函數進行調用,同時把next 本身傳遞給“中間件”作為第三個參數,每個中間件約定的固定形式為 (req, res, next) => {}, 這樣每個“中間件“函數中只要調用 next 方法即可傳遞調用下一個中間件。

之所以說是”尾遞歸“是因為遞歸函數的最后一條語句是調用函數本身,所以每一個中間件的最后一條語句需要是next()才能形成”尾遞歸“,否則就是普通遞歸,”尾遞歸“相對于普通”遞歸“的好處在于節省內存空間,不會形成深度嵌套的函數調用棧。有興趣的可以閱讀下阮老師的尾調用優化

至此,express 的中間件實現就完成了。

koa

不得不說,相比較 express 而言,koa 的整體設計和代碼實現顯得更高級,更精煉;代碼基于ES6 實現,支持generator(async await), 沒有內置的路由實現和任何內置中間件,context 的設計也很是巧妙。

整體

一共只有4個文件:

  • application.js 入口文件,koa應用實例的類
  • context.js ctx 實例,代理了很多request和response的屬性和方法,作為全局對象傳遞
  • request.js koa 對原生 req 對象的封裝
  • response.js koa 對原生 res 對象的封裝

request.js 和 response.js 沒什么可說的,任何 web 框架都會提供req和res 的封裝來簡化處理。所以主要看一下 context.js 和 application.js的實現

// context.js /** * Response delegation. */delegate(proto, 'res') .method('setHeader')/** * Request delegation. */delegate(proto, 'req') .access('url') .setter('href') .getter('ip');

context 就是這類代碼,主要功能就是在做代理,使用了 delegate 庫。

簡單說一下這里代理的含義,比如delegate(proto, 'res').method('setHeader') 這條語句的作用就是:當調用proto.setHeader時,會調用proto.res.setHeader 即,將proto的 setHeader方法代理到proto的res屬性上,其它類似。

// application.js 中部分代碼constructor() { super() this.middleware = [] this.context = Object.create(context)}use(fn) { this.middleware.push(fn)}listen(...args) { debug('listen') const server = http.createServer(this.callback()); return server.listen(...args);}callback() { // 這里即中間件處理代碼 const fn = compose(this.middleware);  const handleRequest = (req, res) => { // ctx 是koa的精髓之一, req, res上的很多方法代理到了ctx上, 基于 ctx 很多問題處理更加方便 const ctx = this.createContext(req, res); return this.handleRequest(ctx, fn); };  return handleRequest;}handleRequest(ctx, fnMiddleware) { ctx.statusCode = 404; const onerror = err => ctx.onerror(err); const handleResponse = () => respond(ctx); return fnMiddleware(ctx).then(handleResponse).catch(onerror);}

同樣的在listen方法中創建 web 服務, 沒有使用 express 那么繞的方式,const server = http.createServer(this.callback()); 用this.callback()生成 web 服務的處理程序

callback 函數返回handleRequest, 所以真正的處理程序是this.handleRequest(ctx, fn)

中間件處理

構造函數 constructor 中維護全局中間件數組 this.middleware和全局的this.context 實例(源碼中還有request,response對象和一些其他輔助屬性)。和 express 不同,因為沒有router的實現,所有this.middleware 中就是普通的”中間件“函數而非復雜的 layer 實例,

this.handleRequest(ctx, fn); 中 ctx 為第一個參數,fn = compose(this.middleware) 作為第二個參數, handleRequest 會調用 fnMiddleware(ctx).then(handleResponse).catch(onerror); 所以中間處理的關鍵在compose方法, 它是一個獨立的包koa-compose, 把它拿了出來看一下里面的內容:

// compose.js'use strict'module.exports = composefunction compose (middleware) { return function (context, next) { let index = -1 return dispatch(0) function dispatch (i) {  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)  } } }}

和express中的next 是不是很像,只不過他是promise形式的,因為要支持異步,所以理解起來就稍微麻煩點:每個中間件是一個async (ctx, next) => {}, 執行后返回的是一個promise, 第二個參數 next的值為 dispatch.bind(null, i + 1) , 用于傳遞”中間件“的執行,一個個中間件向里執行,直到最后一個中間件執行完,resolve 掉,它前一個”中間件“接著執行 await next() 后的代碼,然后 resolve 掉,在不斷向前直到第一個”中間件“ resolve掉,最終使得最外層的promise resolve掉。

這里和express很不同的一點就是koa的響應的處理并不在"中間件"中,而是在中間件執行完返回的promise resolve后:

return fnMiddleware(ctx).then(handleResponse).catch(onerror);

通過 handleResponse 最后對響應做處理,”中間件“會設置ctx.body, handleResponse也會主要處理 ctx.body ,所以 koa 的”洋蔥圈“模型才會成立,await next()后的代碼也會影響到最后的響應。

至此,koa的中間件實現就完成了。

redux

不得不說,redux 的設計思想和源碼實現真的是漂亮,整體代碼量不多,網上已經隨處可見redux的源碼解析,我就不細說了。不過還是要推薦一波官網對中間件部分的敘述 : redux-middleware

這是我讀過的最好的說明文檔,沒有之一,它清晰的說明了 redux middleware 的演化過程,漂亮地演繹了一場從分析問題到解決問題,并不斷優化的思維過程。

總體

本文還是主要看一下它的中間件實現, 先簡單說一下 redux 的核心處理邏輯, createStore 是其入口程序,工廠方法,返回一個 store 實例,store實例的最關鍵的方法就是 dispatch , 而 dispatch 要做的就是一件事:

currentState = currentReducer(currentState, action)

即調用reducer, 傳入當前state和action返回新的state。

所以要模擬基本的 redux 執行只要實現 createStore , dispatch 方法即可。其它的內容如 bindActionCreators, combineReducers 以及 subscribe 監聽都是輔助使用的功能,可以暫時不關注。

中間件處理

然后就到了核心的”中間件" 實現部分即applyMiddleware.js:

// applyMiddleware.jsimport compose from './compose'export default function applyMiddleware(...middlewares) { return createStore => (...args) => { const store = createStore(...args) let dispatch = () => {  throw new Error(  `Dispatching while constructing your middleware is not allowed. ` +   `Other middleware would not be applied to this dispatch.`  ) } const middlewareAPI = {  getState: store.getState,  dispatch: (...args) => dispatch(...args) } const chain = middlewares.map(middleware => middleware(middlewareAPI)) dispatch = compose(...chain)(store.dispatch) return {  ...store,  dispatch } }}

redux 中間件提供的擴展是在 action 發起之后,到達 reducer 之前,它的實現思路就和express 、 koa 有些不同了,它沒有通過封裝 store.dispatch, 在它前面添加 中間件處理程序,而是通過遞歸覆寫 dispatch ,不斷的傳遞上一個覆寫的 dispatch 來實現。

每一個 redux 中間件的形式為 store => next => action => { xxx }

這里主要有兩層函數嵌套:

最外層函數接收參數store, 對應于 applyMiddleware.js 中的處理代碼是 const chain = middlewares.map(middleware => middleware(middlewareAPI)), middlewareAPI 即為傳入的store 。這一層是為了把 store 的 api 傳遞給中間件使用,主要就是兩個api:

  • getState, 直接傳遞store.getState.
  • dispatch: (...args) => dispatch(...args),這里的實現就很巧妙了,并不是store.dispatch, 而是一個外部的變量dispatch, 這個變量最終指向的是覆寫后的dispatch, 這樣做的原因在于,對于 redux-thunk 這樣的異步中間件,內部調用store.dispatch 的時候仍然后走一遍所有“中間件”。

返回的chain就是第二層的數組,數組的每個元素都是這樣一個函數next => action => { xxx }, 這個函數可以理解為 接受一個dispatch返回一個dispatch, 接受的dispatch 是后一個中間件返回的dispatch.

還有一個關鍵函數即 compose, 主要作用是 compose(f, g, h) 返回 () => f(g(h(..args)))

現在在來理解 dispatch = compose(...chain)(store.dispatch) 就相對容易了,原生的 store.dispatch 傳入最后一個“中間件”,返回一個新的 dispatch , 再向外傳遞到前一個中間件,直至返回最終的 dispatch, 當覆寫后的dispatch 調用時,每個“中間件“的執行又是從外向內的”洋蔥圈“模型。

至此,redux中間件就完成了。

其它關鍵點

redux 中間件的實現中還有一點實現也值得學習,為了讓”中間件“只能應用一次,applyMiddleware 并不是作用在 store 實例上,而是作用在 createStore 工廠方法上。怎么理解呢?如果applyMiddleware 是這樣的

(store, middlewares) => {}

那么當多次調用 applyMiddleware(store, middlewares) 的時候會給同一個實例重復添加同樣的中間件。所以 applyMiddleware 的形式是

(...middlewares) => (createStore) => createStore,

這樣,每一次應用中間件時都是創建一個新的實例,避免了中間件重復應用問題。

這種形式會接收 middlewares 返回一個 createStore 的高階方法,這個方法一般被稱為 createStore的 enhance 方法,內部即增加了對中間件的應用,你會發現這個方法和中間件第二層 (dispatch) => dispatch 的形式一致,所以它也可以用于compose 進行多次增強。同時createStore 也有第三個參數enhance 用于內部判斷,自增強。所以 redux 的中間件使用可以有兩種寫法:

第一種:用 applyMiddleware 返回 enhance 增強 createStore

store = applyMiddleware(middleware1, middleware2)(createStore)(reducer, initState)

第二種: createStore 接收一個 enhancer 參數用于自增強

store = createStore(reducer, initState, applyMiddleware(middleware1, middleware2))

第二種使用會顯得直觀點,可讀性更好。

縱觀 redux 的實現,函數式編程體現的淋漓盡致,中間件形式 store => next => action => { xx } 是函數柯里化作用的靈活體現,將多參數化為單參數,可以用于提前固定 store 參數,得到形式更加明確的 dispatch => dispatch,使得 compose得以發揮作用。

總結

總體而言,express 和 koa 的實現很類似,都是next 方法傳遞進行遞歸調用,只不過 koa 是promise 形式。redux 相較前兩者有些許不同,先通過遞歸向外覆寫,形成執行時遞歸向里調用。

總結一下三者關鍵異同點(不僅限于中間件):

  1. 實例創建: express 使用工廠方法, koa是類
  2. koa 實現的語法更高級,使用ES6,支持generator(async await)
  3. koa 沒有內置router, 增加了 ctx 全局對象,整體代碼更簡潔,使用更方便。
  4. koa 中間件的遞歸為 promise形式,express 使用while 循環加 next 尾遞歸
  5. 我更喜歡 redux 的實現,柯里化中間件形式,更簡潔靈活,函數式編程體現的更明顯
  6. redux 以 dispatch 覆寫的方式進行中間件增強

最后再次附上 模擬示例源碼 以供學習參考,喜歡的歡迎star, fork!

回答一個問題

有人說,express 中也可以用 async function 作為中間件用于異步處理? 其實是不可以的,因為 express 的中間件執行是同步的 while 循環,當中間件中同時包含 普通函數 和 async 函數 時,執行順序會打亂,先看這樣一個例子:

function a() { console.log('a')}async function b() { console.log('b') await 1 console.log('c') await 2 console.log('d')}function f() { a() b() console.log('f')}

這里的輸出是 'a' > 'b' > 'f' > 'c'

在普通函數中直接調用async函數, async 函數會同步執行到第一個 await 后的代碼,然后就立即返回一個promise, 等到內部所有 await 的異步完成,整個async函數執行完,promise 才會resolve掉.

所以,通過上述分析 express中間件實現, 如果用async函數做中間件,內部用await做異步處理,那么后面的中間件會先執行,等到 await 后再次調用 next 索引就會超出!,大家可以自己在這里 express async 打開注釋,自己嘗試一下。

以上就是本文的全部內容,希望對大家的學習有所幫助,也希望大家多多支持VeVb武林網。


注:相關教程知識閱讀請移步到JavaScript/Ajax教程頻道。
發表評論 共有條評論
用戶名: 密碼:
驗證碼: 匿名發表
亚洲香蕉成人av网站在线观看_欧美精品成人91久久久久久久_久久久久久久久久久亚洲_热久久视久久精品18亚洲精品_国产精自产拍久久久久久_亚洲色图国产精品_91精品国产网站_中文字幕欧美日韩精品_国产精品久久久久久亚洲调教_国产精品久久一区_性夜试看影院91社区_97在线观看视频国产_68精品久久久久久欧美_欧美精品在线观看_国产精品一区二区久久精品_欧美老女人bb
国产精品高潮粉嫩av| 精品福利一区二区| 亚洲欧美日韩爽爽影院| 色在人av网站天堂精品| 欧美性videos高清精品| 8050国产精品久久久久久| 亚洲精品国产综合区久久久久久久| 亚洲欧美成人网| 尤物yw午夜国产精品视频明星| 亚洲精品国产综合久久| 清纯唯美日韩制服另类| 日韩av在线网| 欧美激情a∨在线视频播放| 国产精品免费小视频| 国产一级揄自揄精品视频| 69久久夜色精品国产69乱青草| 亚洲第一精品久久忘忧草社区| 色综合91久久精品中文字幕| 在线成人激情黄色| 久久久精品免费视频| 久久伊人91精品综合网站| 国产精品久久不能| 国产精品com| 中文字幕最新精品| 日本久久久久久久久久久| 69av在线视频| 国产日产久久高清欧美一区| 国产69久久精品成人| 久久久噜噜噜久久| 亚洲成人网在线| 51精品国产黑色丝袜高跟鞋| 亚洲国产精品va在线观看黑人| 亚洲第一男人天堂| 亚洲色图欧美制服丝袜另类第一页| 久久久精品中文字幕| 上原亚衣av一区二区三区| 国产精品中文字幕在线| 欧美国产日韩二区| 精品中文字幕在线观看| 日本中文字幕成人| 中文字幕一区二区精品| 成人在线观看视频网站| 97国产在线视频| 国产精品视频内| 乱亲女秽乱长久久久| 欧美人在线观看| 18一19gay欧美视频网站| 欧美国产日韩一区| 亚洲一级片在线看| 国产成人一区二| 91久久国产婷婷一区二区| 在线观看日韩视频| 日韩中文字幕在线视频播放| 国产不卡av在线免费观看| 国产女人18毛片水18精品| 亚洲色图五月天| 成人在线精品视频| 久久久视频精品| 久青草国产97香蕉在线视频| 91亚洲精华国产精华| 精品福利樱桃av导航| 国产精品jizz在线观看麻豆| 国产婷婷97碰碰久久人人蜜臀| 亚洲r级在线观看| 亚洲欧美日韩天堂| 亚洲国产日韩精品在线| 日韩欧美精品网站| 欧美成人激情图片网| 国产一区二区三区精品久久久| 91国语精品自产拍在线观看性色| 成人精品久久一区二区三区| 久久精品视频亚洲| 久久91精品国产91久久跳| 亚洲欧美精品一区| 亚洲自拍欧美色图| 亚洲国产第一页| 黄色成人在线免费| 欧美与黑人午夜性猛交久久久| 国产精品久久久久久超碰| 亚洲成人教育av| 国语自产精品视频在线看抢先版图片| 国产一区二区精品丝袜| 97精品一区二区三区| 成人黄色片网站| 国内免费久久久久久久久久久| 午夜精品视频在线| 日韩欧美主播在线| 日韩一中文字幕| 国产精品免费网站| 久久精品亚洲94久久精品| 91国产美女视频| 欧美激情一级精品国产| 国产亚洲视频在线| 136fldh精品导航福利| 亚洲97在线观看| www.xxxx欧美| 97avcom| 国产精品福利在线观看| 91成品人片a无限观看| 亚洲色图15p| 欧美日韩国产综合视频在线观看中文| 91成人国产在线观看| 日本精品一区二区三区在线播放视频| 亚洲va欧美va国产综合剧情| 国产精品亚洲自拍| 亚洲欧美国产制服动漫| 97视频免费在线看| 日韩欧美第一页| 欧美专区日韩视频| 国产日韩欧美综合| 欧美影院在线播放| 欧美精品第一页在线播放| 久久久99免费视频| 国产日韩在线一区| 久久综合国产精品台湾中文娱乐网| 日韩av理论片| 成人av电影天堂| 欧美一级视频一区二区| 九九热在线精品视频| 一区二区三区四区精品| 欧洲亚洲免费在线| 国产亚洲精品久久久久动| 国产精品久久久久久久7电影| 欧美日韩免费观看中文| 国产香蕉精品视频一区二区三区| 久久夜色精品国产| 亚洲天堂日韩电影| 久久久久久久激情视频| 91欧美精品成人综合在线观看| 久久免费精品日本久久中文字幕| 欧洲日本亚洲国产区| 亚洲精品白浆高清久久久久久| www.美女亚洲精品| 国产一区二区在线免费| 中文字幕久热精品在线视频| 欧美激情三级免费| 欧美多人爱爱视频网站| 亚洲色图17p| 国产91|九色| 国产精品99久久久久久白浆小说| 成人自拍性视频| 97av在线播放| 欧美中文字幕在线视频| 久久久久久这里只有精品| 中文字幕免费国产精品| 成人福利在线观看| 日韩中文理论片| 日韩av片电影专区| 91精品啪在线观看麻豆免费| 亚洲香蕉成视频在线观看| 欧美激情伊人电影| 国产精品亚洲视频在线观看| 亚洲欧洲国产伦综合| 欧美性生交大片免网| 亚洲三级av在线| 亚洲国产精品小视频| 久久视频在线观看免费| 伦理中文字幕亚洲| 欧美日韩亚洲精品内裤| 亚洲精品免费av| 最新国产精品拍自在线播放| 97超级碰在线看视频免费在线看| 亚洲精品白浆高清久久久久久| 尤物九九久久国产精品的特点|