express, koa, redux三者中間件簡單對(duì)比分析
Author: AddOneG
Link: http://yoursite.com/2018/09/14/express-koa-redux三者中間件對(duì)比/
這三者對(duì)各自的中間件有著不同的實(shí)現(xiàn),作者本人對(duì)此也比較好奇,在這里小小的研究一下源碼,探究三者之間的異同
什么是中間件
在我看來,中間件就是在你的代碼運(yùn)行中進(jìn)行一些修改的工具。比如你想喝水,那么喝水之前你將水凈化就可以理解為是一次中間件的執(zhí)行。他不是插件,獨(dú)立于程序之外,而更像是在你的代碼中表現(xiàn)一種類似連接的功能
Koa 與 Express 中間件概述
這兩者都是Node層面的,這里我們根據(jù)官方文檔來對(duì)比
Express
var app = express();
// 沒有掛載路徑的中間件,應(yīng)用的每個(gè)請(qǐng)求都會(huì)執(zhí)行該中間件
app.use(function (req, res, next) {
console.log('Time:', Date.now());
next();
});
// 掛載至 /user/:id 的中間件,任何指向 /user/:id 的請(qǐng)求都會(huì)執(zhí)行它
app.use('/user/:id', function (req, res, next) {
console.log('Request Type:', req.method);
next();
});
// 路由和句柄函數(shù)(中間件系統(tǒng)),處理指向 /user/:id 的 GET 請(qǐng)求
app.get('/user/:id', function (req, res, next) {
res.send('USER');
});
可以看到express的中間件是使用next進(jìn)行線性調(diào)用的,一個(gè)接著一個(gè)的執(zhí)行,是一種尾遞歸的調(diào)用(后文會(huì)講)。然后在最后一個(gè)中間件中進(jìn)行對(duì)response的處理(習(xí)慣)
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的中間件絕對(duì)不是線性的,因?yàn)橐坏┦褂昧薬wait,代碼就會(huì)停止當(dāng)前中間件的執(zhí)行轉(zhuǎn)而去執(zhí)行await后面的代碼,這里next表示下一個(gè)中間件。所以這是一個(gè)支持generator的洋蔥圈模型(后文會(huì)講)
Koa 與 Express 中間件源碼進(jìn)一步解析
上面提到,express的中間件是尾遞歸調(diào)用,而koa的中間件因?yàn)槭褂昧薬wait所以是支持generator的洋蔥圈模型,這里以此展開來分析代碼
Express
我們直接進(jìn)入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中的中間件并且將自己傳遞給中間件作為參數(shù),這樣中間件只需要調(diào)用next方法就能不斷傳遞到下一個(gè)中間件。在函數(shù)的末尾遞歸調(diào)用了next方法,所以稱為尾遞歸調(diào)用
Koa
Koa對(duì)中間件的處理是在一個(gè)獨(dú)立的包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來支持異步,這里不停調(diào)用dispatch.bind(null, i + 1)傳遞下一個(gè)中間件,一個(gè)一個(gè)中間件向里執(zhí)行,直到最后一個(gè)中間件執(zhí)行完resolve掉,然后不斷向前resolve中間件,直到第一個(gè)中間件被resolve。我們可以發(fā)現(xiàn),相應(yīng)的處理并不在中間件中而是在其resolve后
Redux
對(duì)于redux的基礎(chǔ)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中包含兩個(gè)api,一個(gè)是store的getState;另一個(gè)是覆寫的dispath,這是一個(gè)外部變量,最終指向覆寫后的dispach,對(duì)于compose的作用是compose(f, g, h) 返回 () => f(g(h(..args)))
那么dispatch = compose(...chain)(store.dispatch)即原生的 store.dispatch 傳入最后一個(gè)“中間件”,返回一個(gè)新的dispatch ``, 再向外傳遞到前一個(gè)中間件,直至返回最終的dispatch`, 當(dāng)覆寫后的dispatch調(diào)用時(shí),每個(gè)“中間件“的執(zhí)行又是從外向內(nèi)的”洋蔥圈“模型
1.看到這里了就點(diǎn)個(gè)在看支持下吧,你的「點(diǎn)贊,在看」是我創(chuàng)作的動(dòng)力。
2.關(guān)注公眾號(hào)
程序員成長指北,回復(fù)「1」加入高級(jí)前端交流群!「在這里有好多 前端 開發(fā)者,會(huì)討論 前端 Node 知識(shí),互相學(xué)習(xí)」!3.也可添加微信【ikoala520】,一起成長。
“在看轉(zhuǎn)發(fā)”是最大的支持
