前言
最早提出 Middleware 概念的是 Express,随后由原班人马打造的 Koa 不但沿用了 Middleware 的架构设计,还更加彻底的把自己定义为中间件框架。Redux也引入了 Middleware 的概念,方便独立功能的函数对 Action 进行处理。Axios虽然没有中间件,但其拦截器的用法却跟中间件十分相似。本文结合使用场景,拆解对比各大框架的 Middleware 的实现原理, 。
Middleware
Middleware(中间件)本意是指位于服务器的操作系统之上,管理计算资源和网络通信的一种通用独立的系统软件服务程序。分布式应用软件借助这种软件在不同的技术之间共享资源。
而大前端领域,Middleware 一般指提供通用独立功能的数据处理函数,包括日志记录、数据叠加和错误处理等。
Express
Express 中应用层级的中间件的注册方式:
const stack = [];
/** 通过 use 注册 */
function use(fn) {
stack.push(fn);
}
/** 请求到达的时候,会触发handle方法。接着next函数从队列中顺序取出并执行 */
function handle(req, res) {
var idx = 0;
next();
function next() {
var fn = stack[idx++];
fn(req, res, next)
}
}
Koa
Koa的 Middleware 注册跟路由无关,所有的请求都会经过注册的中间件。同时Koa 支持async/await异步编程模式:
/** 注册 */
function use(fn) {
// 省略部分代码...
this.middleware.push(fn);
return this;
}
Koa 的 Middleware 顺序执行,通过?dispatch函数来控制,compose 函数对已注册的中间件列表(middleware)栈内每一个中间件函数的校验,并返回?fn? 函数。
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!');
}
/**
* @param {Object} ctx
* @return {Promise}
* @api public
*/
return function fn (ctx, next) {
return dispatch(0);
function dispatch (i) {
let middlewareFn = middleware[i]
try {
return Promise.resolve(middlewareFn(ctx, dispatch.bind(null, i + 1)));
} catch (err) {
return Promise.reject(err);
}
}
}
}
Redux
Redux中间件的参数经过柯里化,store是applyMiddleware内部传进来的,next是compose后传进来的,action是dispatch传进来的
export default function applyMiddleware(...middlewares) {
return (createStore) =>(reducer, preloadedState) => {
const store = createStore(reducer, preloadedState)
let dispatch = store.dispatch;
let chain = [];
const middlewareAPI = {
getState: store.getState,
dispatch: (action) => dispatch(action)
}
/** 先执行一遍middleware,把第一个参数store传进去 */
chain = middlewares.map(middleware => middleware(middlewareAPI));
/** 传入原始的dispatch */
dispatch = compose(...chain)(store.dispatch)
return {
...store,
dispatch
}
}
}
这里 compose 的返回值又重新赋值给dispatch:
function compose (...funcs) {
if (funcs.length === 0) {
return arg => arg
}
if (funcs.length === 1) {
return funcs[0]
}
return funcs.reduce((a, b) =>(...args) => a(b(...args)))
}
说明我们在应用内调用的dispatch并不是store自带的,而是经过 Middleware 处理的升级版。
Axios
axios没有中间件,但有类似功能的拦截器(interceptors),本质上都是在数据处理链路的 2 点之间,提供独立的、配置化的、可叠加的额外功能。
function Axios(instanceConfig) {
this.defaults = instanceConfig;
this.interceptors = {
request: new InterceptorManager(),
response: new InterceptorManager()
};
}
function InterceptorManager() {
this.handlers = [];
}
InterceptorManager.prototype.use = function use(fulfilled, rejected) {
this.handlers.push({
fulfilled: fulfilled,
rejected: rejected
});
return this.handlers.length - 1;
};
Axios内部会维护 2 个 interceptors,它们有独立的 handlers 数组。use就是往数组添加元素而已,跟其它框架不同的是这里的数组元素不是一个函数,而是一个对象,包含fulfilled和rejected 2 个属性。第二个参数不传的时候rejected就是 undefined。
通过 promise 的链式调用,将 interceptors 串联了起来,执行顺序是:requestInterceptorChain -> chain -> responseInterceptorChain。这里有一个默认的约定,chain 里的元素都是按照[fulfilled1, rejected1, fulfilled2, rejected2]这种模式排列的,所以注册 interceptors 的时候如果没有提供第二个参数,也会有一个默认值 undefined:
Axios.prototype.request = function request(config) {
config = mergeConfig(this.defaults, config);
// 成对的添加元素
var requestInterceptorChain = [];
this.interceptors.request.forEach(function unshiftRequestInterceptors(interceptor) {
requestInterceptorChain.unshift(interceptor.fulfilled, interceptor.rejected);
});
var responseInterceptorChain = [];
this.interceptors.response.forEach(function pushResponseInterceptors(interceptor) {
responseInterceptorChain.push(interceptor.fulfilled, interceptor.rejected);
});
var chain = [dispatchRequest, undefined];
Array.prototype.unshift.apply(chain, requestInterceptorChain);
chain.concat(responseInterceptorChain);
promise = Promise.resolve(config);
while (chain.length) {
promise = promise.then(chain.shift(), chain.shift());
}
return promise;
}
总结
这里面最精妙也是最难理解的就是Array.reduce这种形式,需要反复的推敲。promise.then链式调用的任务编排方法也十分巧妙,前面处理完的数据会自动传给下一个then。递归调用的形式则最好理解,Koa在Express实现的基础上天然支持异步调用,更符合服务器端场景?
|