理解前端的 Middleware 原理和实现
前言
最早提出 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实现的基础上天然支持异步调用,更符合服务器端场景
这篇好文章是转载于:学新通技术网
- 版权申明: 本站部分内容来自互联网,仅供学习及演示用,请勿用于商业和其他非法用途。如果侵犯了您的权益请与我们联系,请提供相关证据及您的身份证明,我们将在收到邮件后48小时内删除。
- 本站站名: 学新通技术网
- 本文地址: /boutique/detail/tanhiaegcj
-
photoshop保存的图片太大微信发不了怎么办
PHP中文网 06-15 -
word里面弄一个表格后上面的标题会跑到下面怎么办
PHP中文网 06-20 -
photoshop扩展功能面板显示灰色怎么办
PHP中文网 06-14 -
《学习通》视频自动暂停处理方法
HelloWorld317 07-05 -
Android 11 保存文件到外部存储,并分享文件
Luke 10-12 -
TikTok加速器哪个好免费的TK加速器推荐
TK小达人 10-01 -
微信公众号没有声音提示怎么办
PHP中文网 03-31 -
excel下划线不显示怎么办
PHP中文网 06-23 -
excel打印预览压线压字怎么办
PHP中文网 06-22 -
微信运动停用后别人还能看到步数吗
PHP中文网 07-22