Axios 是一个基于 promise 的 HTTP 库,可以用在浏览器和 node.js 中。
axios功能
- 在浏览器环境创建 XMLHttpRequests
- 在 node.js 创建 http 请求
- 支持promise API
- 拦截请求和响应
- 转换请求数据和响应数据
- 取消请求
- 自动转换 JSON 数据
- 客户端支持防御 XSRF
通过源码来理清这些功能的实现原理
axios使用
中文说明文档: https://www.kancloud.cn/yunye/axios/234845
执行get请求:
1 | // 为给定 ID 的 user 创建请求 |
指向post请求:
1 | axios.post('/user', { |
源码阅读笔记
1.axios/lib/axios.js
1 | ; |
**2.axios/lib/util.js方法
1 | module.exports = { |
is开头的isXxx方法名 都是判断是否是 Xxx 类型
extend 将 b 里面的属性和方法继承给 a , 并且将 b 里面的方法的执行上个下文都绑定到 thisArg
1 | function extend(a, b, thisArg) { |
extent中有个bind方法,在axios/lib/helpers/bind.js
1 | module.exports = function bind(fn, thisArg) { |
自定义 forEach 方法支持遍历基本数据,数组,对象。
1 | function forEach(obj, fn) { |
merge 合并对象的属性,相同属性后面的替换前的
1 | function merge(/* obj1, obj2, obj3, ... */) { |
回看上面的重点方法 createInstance
1 | function createInstance(defaultConfig) { |
createInstance 函数返回了一个函数 instance.
- instance 是一个函数 Axios.prototype.request 且执行上下文绑定到 context。
- instance 里面还有 Axios.prototype 上面的所有方法,并且这些方法的执行上下文也绑定到 context。
- instance 里面还有 context 上的方法。
2.axios/lib/core/Axios.js
1 | ; |
上面的所有的方法都是通过调用了 this.request 方法,使用到了 Promise 的链式调用,也使用到了中间件的思想。
1 | function Axios(instanceConfig) { |
拦截器的作用就是在请求或响应被 then 或 catch 处理前拦截它们,大致用法如下:
1 | // 添加请求拦截器 |
通过 promise 链式调用一个一个函数,这个函数就是 chain 数组里面的方法
1 | // 初始的 chain 数组 dispatchRequest 是发送请求的方法 |
- chain是一个执行队列。这个队列的初始值,是一个带有config参数的Promise
- 在chain执行队列中,插入了初始的发送请求的函数dispatchReqeust和与之对应的undefined。后面需要增加一个undefined是因为在Promise中,需要一个success和一个fail的回调函数,这个从代码promise = promise.then(chain.shift(), chain.shift());就能够看出来。因此,dispatchReqeust和undefined我们可以成为一对函数。
- 在chain执行队列中,发送请求的函数dispatchReqeust是处于中间的位置。它的前面是请求拦截器,通过unshift方法放入;它的后面是响应拦截器,通过push放入。要注意的是,这些函数都是成对的放入,也就是一次放入两个。
3.axios/lib/core/InterceptorManager.js
1 | ; |
4.axios/lib/core/dispatchRequest.js
1 | ; |
在最开始实例化 createInstance 方法中我们传入的 defaults是什么?
5.axios/lib/defaults.js
1 | ; |
6.axios/lib/cancel
取消http请求:在完成搜索相关的功能时,我们经常会需要频繁的发送请求来进行数据查询的情况。通常来说,我们在下一次请求发送时,就需要取消上一次请求。因此,取消请求相关的功能也是一个优点。
1 | const CancelToken = axios.CancelToken; |
首先是元数据Cancel类,它是用来记录取消状态一个类
1 | ; |
在CancelToken类中,它通过传递一个Promise的方法来实现了HTTP请求取消
1 | ; |
在adapter/xhr.js文件中,有与之相对应的取消请求的代码:
1 | if (config.cancelToken) { |
结合代码总结实现逻辑如下:
- 在可能需要取消的请求中,初始化时调用了source方法,这个方法返回了一个CancelToken类的实例A和一个函数cancel。
- 在source方法返回实例A中,初始化了一个在pending状态的promise。将整个实例A传递给axios后,这个promise被用于做取消请求的触发器。
- 当source方法返回的cancel方法被调用时,实例A中的promise状态由pending变成了fulfilled,立刻触发了then的回调函数,从而触发了axios的取消逻辑——request.abort(),非常巧妙~
axios的设计值的借鉴之处
- 发送请求函数的处理逻辑
axios在处理发送请求的dispatchRequest函数时,没有当做一个特殊的函数来对待,而是采用一视同仁的方法,将其放在队列的中间位置,从而保证了队列处理的一致性,提高了代码的可阅读性。
- Adapter的处理逻辑
在adapter的处理逻辑中,axios没有把http和xhr两个模块(一个用于Node.js发送请求,另一个则用于浏览器端发送请求)当成自身的模块直接在dispatchRequest中直接饮用,而是通过配置的方法在default.js文件中进行默认引入。这样既保证了两个模块间的低耦合性,同时又能够为今后用户需要自定义请求发送模块保留了余地。
- 取消HTTP请求的处理逻辑
在取消HTTP请求的逻辑中,axios巧妙的使用了一个Promise来作为触发器,将resolve函数通过callback中参数的形式传递到了外部。这样既能够保证内部逻辑的连贯性,也能够保证在需要进行取消请求时,不需要直接进行相关类的示例数据改动,最大程度上避免了侵入其他的模块。
彩蛋:如何取消上一个页面所有请求
SPA时代经常会遇到当前页面未加载完毕时跳转路由或者返回操作, 但是通过network会发现, 若网络环境较差的情况下, 会一直pending, 切换路由后在network中添加新的请求但是正在pending的请求依然存在. 当我们在项目中做了一个上拉加载分页的时候会一直加载中, 用户等待不耐烦后可能会主动触发返回操作, 但是此刻即使用户触发返回操作, 加载分页的请求还是存在, 页面还是会一直提示加载中, 直到该请求加载成功或超时才肯罢休
实现的思路是将cancel放置于全局变量的数组中, 可以挂在至window对象上或Vue示例上
1 | http.interceptors.request.use(function (config) { |
在路由切换时进行操作
1 | router.beforeEach((to, from, next) => { |
参考资料:
https://juejin.im/post/5aedd4a2f265da0b9d781b85
axios源码:https://github.com/axios/axios