理解前端的 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实现的基础上天然支持异步调用,更符合服务器端场景
理解前端的 Middleware 原理与实现相关推荐
- 理解前端Babel编译原理
大厂技术 坚持周更 精选好文 背景 我们知道编程语言主要分为「编译型语言」和「解释型语言」,编译型语言是在代码运行前编译器将编程语言转换成机器语言,运行时不需要重新翻译,直接使用编译的结果就行了. ...
- 你如何理解前端的工作(面试题)
入坑前端到今天也将近两年半了,这两天突然想到了第一次面试时面试官的一个问题-------你怎样理解前端的工作? 对于当时我一个小白而言完全是胡说一通,词不达意,搞得面试官一脸懵逼,现在想想那可能就叫尬 ...
- Vue模板语法(理解前端渲染)
目录 如何理解前端渲染: 前端渲染方式: 1.原生js拼接字符串: 2.使用前端模板引擎 模板语法概括: 指令? v-cloak解决闪动问题. 数据绑定指令 数据响应式: 双向数据绑定: 事 ...
- 前端路由工作原理与使用
单页应用和多页应用 单页面应用:所有功能在一个页面上实现 一个.html 文件 前端路由 组件化开发 网易云音乐 小米移动端 多页应用:与单页应用相对应的,不同的功能通过不同的页面来实现 单页面 - ...
- 大前端的技术原理和变迁史
本文适合前端新手入门,阅读人群最好是前端新手或者后台开发人员,因为我不敢保证对前端老司机有太多收获. 通过阅读本文,你将会大致了解前端这些年发生的事情,以及一些前端当前主流技术的简单原理介绍.所有涉及 ...
- [diango]理解django视图工作原理
前言:正确理解django视图view,模型model,模板的概念及其之间的关联关系,才能快速学习并上手使用django制作网页 本文主要讲解自己在学习django后对视图view的理解 在进入正文之 ...
- 深入理解CPU的调度原理
前言 软件工程师们总习惯把OS(Operating System,操作系统)当成是一个非常值得信赖的管家,我们只管把程序托管到OS上运行,却很少深入了解操作系统的运行原理.确实,OS作为一个通用的软件 ...
- 京东面试官:你是怎么理解 MySQL 的优化原理的?
说起MySQL的查询优化,相信大家收藏了一堆奇技淫巧:不能使用 SELECT*.不使用NULL字段.合理创建索引.为字段选择合适的数据类型..... 你是否真的理解这些优化技巧?是否理解其背后的工作原 ...
- 【分布式ID】理解Snowflake算法的实现原理
1.概述 转载:冷饭新炒:理解Snowflake算法的实现原理 我上次也看了一个视频讲解:[分布式ID]键高并发 分布式 全局唯一 ID 雪花算法 snowflake 2.前提# Snowflake( ...
最新文章
- gin context和官方context_gin 源码阅读(二) 路由和路由组
- 一家美资企业的java servlet面试题
- 【Alpha】第二次Scrum meeting
- 高等数学上-赵立军-北京大学出版社-题解-练习2.6
- Echarts报错:Component series.lines not exists. Load it first.
- failed to keep to the max pss of 66560
- lcd驱动解析(一)
- 由sock引起的感想
- 什么是PaaS云平台?
- hibernate历史版本下载
- 动态视频壁纸多功能工具箱微信小程序源码,支持外卖CPS和流量主
- 计算机单片机实训报告,单片机实训报告范文
- abp The value could not be converted to a GUID:
- 瞎琢磨先生のJava笔记之Java代码远程调用shell脚本
- 怎样查找计算机死机日志,死机和日志错误
- 使用功能点估算模型评估软件测试的工作量
- http 网络异常请求处理
- 什么是UI(UI百科)
- JZ-008-跳台阶
- SLUB和SLAB的区别
热门文章
- es带用户名密码验证并配置elasticsearch-head连接
- 既然来到了这个世界,何必还是那么认真
- android下保存图片到mySQL_android将图片保存进数据库
- 《SQL Server 2008从入门到精通》--20180627
- 仅需三步就可以把代码块完美插入到word中
- 2年内成准独角兽,影刀RPA登榜2021杭州独角兽准独角兽企业榜单
- Ralink无线驱动 有SoftAP_Mode ,STA_Mode
- 转载:天涯——散文天下——《自我介绍》——作者:南方孤驴
- 关于心理的二十五种倾向(查理·芒格)-2
- 城市经济发展——城市化与第三产业的发展 考题答案