在Koa2中,中間件被存放在一個數組中。 使用koa中,最多見的就是app.use(fn),use函數部分源碼以下所示。首先中間件必須是個函數。如果generator函數,則須要進行轉化。最後把該中間件推入middelaware數組中。javascript
constructor() {
this.middleware = [];
}
use(fn) {
this.middleware.push(fn);
}
複製代碼
當調用app.listen函數時,其實是建立了一個原生http服務器,並執行了Koa自身的callback方法。java
listen(...args) {
const server = http.createServer(this.callback());
return server.listen(...args);
}
複製代碼
callback函數中compose是將中間件封裝成一個迭代器,按照middleware數組中順序執行下去,實現了面向切面編程。handleRequest函數則是將req和res封裝成ctx,並執行中間件。編程
callback() {
const fn = compose(this.middleware);
const handleRequest = (req, res) => {
const ctx = this.createContext(req, res);
return this.handleRequest(ctx, fn);
};
return handleRequest;
}
handleRequest(ctx, fnMiddleware) {
...
return fnMiddleware(ctx).then(handleResponse).catch(onerror);
}
複製代碼
compose用來返回一個迭代器函數fnMiddleware,該函數接受兩個參數,context和next。由於調用時只向fnMiddleware傳入了第一個參數context,因此next參數的值一直是undefined。數組
首先定義了index,dispatch傳入的參數 i 做爲每一箇中間件的調用標誌,每次中間件調用next函數都會讓index+1,如果中間件屢次調用next,則會使index大於等於該標誌,就會報錯。bash
而後依次讀取中間件數組中的中間件,並將context和封裝了調用標誌i的dispatch函數傳給中間件,就實現了中間件的過程。
當讀取完中間件數組後,即i === middleware.length時,將值爲是undefined的next傳給fn並結束迭代。dispatch函數全部的返回值都是Promise函數,這樣就能夠實現異步編程。服務器
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)
}
}
}
}
複製代碼
在Redux中須要引入中間件的話,需引入applyMiddleware函數並將其做爲參數傳給createStore。下方enhancer就是就是applyMiddleware的返回值。app
export default function createStore(reducer, preloadedState, enhancer) {
...
return enhancer(createStore)(reducer, preloadedState)
}
複製代碼
applyMiddleware接受一箇中間件數組爲參數。koa
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
}
}
}
複製代碼
其中compose用Array.prototype.reduce將中間件數組封裝成一個層層包裹的函數。異步
function compose(...funcs) {
return funcs.reduce((a, b) => (...args) => a(b(...args)))
}
複製代碼
若原本的中間件數組是異步編程
[a, b, c]
通過compose函數封裝後,就成了
(...arg) => a(b(c(...arg)))
dispatch = compose(...chain)(store.dispatch)
複製代碼
通過這個過程後,每次調用dispatch就會在中間件中進行遍歷。每一箇中間件都須要調用next(action)來保證中間件鏈都能被執行。
Redux中中間件的寫法通常爲
const middleware = store => next => action => {...}
複製代碼
根據上面的分析,store參數就是middlewareAPI,可是其中的dispatch並非真正的dispatch,這是爲了防止在中間件中調用store.dispatch而致使從新遍歷整個中間件鏈。next是下一個中間件,須要傳遞action,直到最後一箇中間件時,next便是原始的store.dispatch。
在二者中都出現了compose函數。 Koa2中的compose函數實現原理是用dispatch函數自身迭代,是從左向右執行中間件函數。而Redux中的compose實現原理是用了數組的reduce方法,從右向左執行中間件函數。二者執行順序雖然不一樣,同樣的是先執行的中間件能夠獲取後執行的中間件的狀態(store的狀態或者context的狀態),實現了面向切面編程。