Author: AddOneG前端
Link: http://yoursite.com/2018/09/14/express-koa-redux三者中間件對比/web
這三者對各自的中間件有着不一樣的實現,做者本人對此也比較好奇,在這裏小小的研究一下源碼,探究三者之間的異同express
什麼是中間件
在我看來,中間件就是在你的代碼運行中進行一些修改的工具。好比你想喝水,那麼喝水以前你將水淨化就能夠理解爲是一次中間件的執行。他不是插件,獨立於程序以外,而更像是在你的代碼中表現一種相似鏈接的功能redux
Koa 與 Express 中間件概述
這二者都是Node層面的,這裏咱們根據官方文檔來對比api
Express
var app = express();
// 沒有掛載路徑的中間件,應用的每一個請求都會執行該中間件
app.use(function (req, res, next) {
console.log('Time:', Date.now());
next();
});
// 掛載至 /user/:id 的中間件,任何指向 /user/:id 的請求都會執行它
app.use('/user/:id', function (req, res, next) {
console.log('Request Type:', req.method);
next();
});
// 路由和句柄函數(中間件系統),處理指向 /user/:id 的 GET 請求
app.get('/user/:id', function (req, res, next) {
res.send('USER');
});
能夠看到express的中間件是使用next進行線性調用的,一個接着一個的執行,是一種尾遞歸的調用(後文會講)。而後在最後一箇中間件中進行對response的處理(習慣)微信
Koa
const Koa = require('koa');
const app = new Koa();
// x-response-time
app.use(async (ctx, next) => {
const start = Date.now();
await next();
const ms = Date.now() - start;
ctx.set('X-Response-Time', `${ms}ms`);
});
// logger
app.use(async (ctx, next) => {
const start = Date.now();
await next();
const ms = Date.now() - start;
console.log(`${ctx.method} ${ctx.url} - ${ms}`);
});
// response
app.use(async ctx => {
ctx.body = 'Hello World';
});
app.listen(3000);
從代碼中的await能夠看出,koa的中間件絕對不是線性的
,由於一旦使用了await,代碼就會中止當前中間件的執行轉而去執行await後面的代碼,這裏next
表示下一個中間件
。因此這是一個支持generator的洋蔥圈模型(後文會講)app
Koa 與 Express 中間件源碼進一步解析
上面提到,express的中間件是尾遞歸調用,而koa的中間件由於使用了await因此是支持generator的洋蔥圈模型,這裏以此展開來分析代碼koa
Express
咱們直接進入application.js中觀察中間件處理異步
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方法就能不斷傳遞到下一個中間件。在函數的末尾遞歸調用了next方法,因此稱爲尾遞歸調用async
Koa
Koa對中間件的處理是在一個獨立的包koa-compose中
'use strict'
module.exports = compose
function 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)
}
}
}
}
Koa中使用了Promise來支持異步,這裏不停調用dispatch.bind(null, i + 1)傳遞下一個中間件,一個一箇中間件向裏執行,直到最後一箇中間件執行完resolve掉,而後不斷向前resolve中間件,直到第一個中間件被resolve。咱們能夠發現,相應的處理並不在中間件中而是在其resolve後
Redux
對於redux的基礎createStore,reducer,dispatch等就不解釋了,這> 裏直接看applyMiddleware的代碼
import 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
}
}
}
這裏仍是比較好理解的,middlewareAPI中包含兩個api,一個是store的getState;另外一個是覆寫的dispath,這是一個外部變量,最終指向覆寫後的dispach,對於compose的做用是compose(f, g, h) 返回 () => f(g(h(..args)))
那麼dispatch = compose(...chain)(store.dispatch)即原生的 store.dispatch
傳入最後一個「中間件」,返回一個新的dispatch ``, 再向外傳遞到前一箇中間件,直至返回最終的
dispatch`, 當覆寫後的dispatch調用時,每一個「中間件「的執行又是從外向內的」洋蔥圈「模型
在線筆記
-
最近花了點時間把筆記整理到語雀上了,方便同窗們閱讀:公衆號回覆筆記或者簡歷
最後
1.看到這裏了就點個在看支持下吧,你的
「點贊,在看」
是我創做的動力。2.關注公衆號
前端壹棧
,回覆「1」加入前端交流羣
!「在這裏有好多前端開發者,會討論前端知識,互相學習」!3.也可添加公衆號
【前端壹棧】
,一塊兒成長
本文分享自微信公衆號 - 前端壹棧(Ecmscript)。
若有侵權,請聯繫 support@oschina.cn 刪除。
本文參與「OSC源創計劃」,歡迎正在閱讀的你也加入,一塊兒分享。