源码分析axios拦截器实现思路
一、前言
为什么要看这个呢,因为前段时间有人提了个问题,axios
的 拦截器如果想取消是否还需要return
以及return
什么值的问题,带着这个问题,看了axios
的源码
说是源码分析,其实就是我看代码以及梳理思路的过程,哈哈哈
个人感觉
axios
的代码规范做的很好,命名规范,注释简洁,建议亲自品尝!
二、准备工作
为了更好地调试代码,我觉得还是需要一个环境的,因此我基于json-server
和webpack-dev-server
搭建了一个测试环境
选择webpack
的原因很简单,我只会webpack
。。
选择json-server
的原因是快,其实基于express
搭建一个服务也很快,但是写接口还是要费一定时间的。
json-server
只需要一个Json文件就可以搭建一个RESTful API,非常优雅,【json-server】
项目结构和搭建过程在附录,需要的自行查看吧,可以自己搭建就直接忽视这一步
三、整体思路
1. 纵观
源码是在
node_modules
中拿的,为了偷懒npm i一下就可以直接测试,直接进入正题,梳理代码
最外层入口文件index.js
只有一行代码
1 | module.exports = require('./lib/axios'); |
追踪到axios.js
1 | function createInstance(defaultConfig) { |
实际也就做了一件事,执行了createInstance
方法,创建了一个Axios
的实例,在实例上绑定了各种属性,如spread
、all
、Cancel
等等,最后抛出这个实例。
所以切入点就是这个createInstance
方法了,参数来自default.js
,根据名字猜测应该就是axios默认配置,出于好奇看一眼
1 | var defaults = { |
代码精简后就是这个东西,其实就是axios
的默认配置,也支持用户自定义,也就是create
方法的参数
继续向下,将这个配置作为参数new
了Axios
的一个实例,看一眼Axios
这个文件,感觉像是核心部分
1 | function Axios(instanceConfig) { |
构造函数其实没做啥,就把默认配置绑定到defaults
属性上,又给interceptors
了一个空的默认值,其实这个interceptors
就是我们核心要看的拦截器。
如果没注册拦截器,默认就不拦截,相当于
ajax
套了一层promise
的壳,后面分析就明白了
然后在Axios原型上声明了一个request属性,不知道干嘛用的。
继续向下,通过自定义的循环函数分别在Axios原型上绑定了delete
,get
,post
等几个函数,值是刚才说到的不知道干嘛用的request
函数,不过根据我多年代码经验,axios.get
就是发送get
请求,这里的request
莫非就是发送请求?
带着疑问继续看axios.js
中createInstance
函数的剩余部分
1 | var instance = bind(Axios.prototype.request, context); |
这句也出现了原型上的request
,看来这函数挺重要的,看一下bind
函数都做了什么,js
中原生的bind
是绑定上下文,这里也用了这个名字,猜测效果差不多
1 | module.exports = function bind(fn, thisArg) { |
果然,只不过这里的区别是在实例外面又套了一层函数,在这个函数里面执行了前面那个request
方法,这样就可以通过axios(‘url’)访问了,也解决了我最初看代码的一个疑问
axios
既然是一个实例,为什么能通过axios('/api/login');
调用?原来是bind函数本身又返回了一个函数
众望所归我们该去看Axios.prototype.request
了
2. 入微
1 | Axios.prototype.request = function request(config) { |
核心部分在于chain
这个拦截器队列,也就是实现请求和响应拦截的核心
chain
默认包含两个元素,分别是真正执行请求的函数dispatchRequest
和一个undefined
,然后遍历请求拦截器和响应拦截器从队列的“首部”和“尾部”入队
这样形成了一种管道的结构,执行时依次执行请求拦截器,真正的请求,最后经过响应的拦截器
也就是这段代码
1 | while (chain.length) { |
每次循环都从队列出队两个,分别作为Promise的成功和失败的回调函数,然后promise的执行结果作为下一次的参数,很巧妙的设计!
关于chain初始值的思考:
最初以为[
dispatchRequest
,undefined
]是为了凑一对,为了一次出队两个元素,后来发现并不是,undefined是作为请求的错误处理函数,如果此处不是undefined,则请求抛出的异常无法在响应拦截器中拦截到,后面分析取消请求会具体分析
继续分析,如果没有配置拦截器,则会默认执行dispatchRequest
发送请求
这里面有个关于Promise的知识点:
如果
promise1
的参数是promise,则promise1
的结果取决于promise,也就是说如果promise的结果是fulfilled,那么promise1的结果也是fulfilled所以才可以形成一种promise链的感觉
3. 芥子
接下来是dispatchRequest
,也就是实际发送请求的部分,也就是说如果不配置拦截器,只会执行这部分代码
这个函数主要做的事是:
根据当前环境决定用哪个
adapter
发送请求在发送请求之前处理数据
请求发送之后处理响应数据
如果配置了取消请求,则也会在这里处理,这篇文章不分析了,其实就是在请求promise中抛出异常来终止promise链
1 | module.exports = function dispatchRequest(config) { |
如果请求正常则返回一个promise
,交给下面的响应拦截器
如果请求失败则返回rejection
状态的promise
,由用户自行通过catch
块捕获异常
根据这个理论,如果有些异常不想统一处理,可以通过
Promise.reject
将错误在响应拦截器中抛出去。这是因为
promise
的异常有冒泡
的特性,如果没人捕获这个异常,将持续向外冒泡,由最终的接盘侠
接手哈哈
所以我们响应拦截器可以这么写
1 | // 测试请求 |
4. 浩瀚
关于Promise
“链”的解释
先上个demo
1 | function handlerErr(err) { |
这就是axios拦截器的核心实现
通过调试这段代码你就知道promise链式如何工作了
几种尝试:
```javascript
chain = [requestInterceptor, handlerErr, request, undefined, responseInterceptor, handlerErr];1
2
3
4
5
将request的错误处理函数变成undefined,看看在最后一个异常处理函数中能否捕获到异常
* ```javascript
chain = [requestInterceptor, handlerErr, request, undefined, responseInterceptor, undefined];将最后一个异常处理函数也变成undefined,看看控制台如何输出
chain保持第二种情况,在promise.then中捕获异常看看是否能捕获到
这几种问题搞明白,基本axios拦截器原理你就明白了!
四、总结
历时一天多,代码难度不大,顺便复习了promise
的一些用法
阅读代码的过程还是很愉悦的,可能是很喜欢作者的代码注释,但是作者的回调函数都没用匿名函数,总觉得怪怪的
五、附录:测试环境搭建
项目结构贴个地址吧,手打项目结构太痛苦了
https://gitee.com/rambler1501719577/learn-axios-source
1. JSON-Server
json-server的环境可以根据官网的配置来,只需要一个依赖加一个json文件
1 | npm i json-server -g |
然后再json文件的根目录通过
1 | json-server --watch 你的json文件名 |
然后就可以通过localhost:3000
访问了
2. webpack环境
webpack的环境需要安装webpack,webpack-cli和webpack-dev-server
1 | npm i webpack webpack-cli webpack-dev-server -D |
然后在项目根目录新建webpack.config.js
,写入以下内容
1 | const { resolve } = require('path') |
然后在项目根目录运行
1 | npx webpack-dev-server |
然后就可以通过localhost:9000
访问你的项目了