一、前言
本篇文章我们继续探究axios源码实现原理,这一篇中会重点介绍取消请求的实现原理。
如果你没有看过第一篇和第二篇:
新年假期弯道超车:每天都在使用的Axios源码竟然这么简单?---发送请求篇(一)
新年假期弯道超车:每天都在使用的Axios源码竟然这么简单?---请求响应拦截器篇(二)
建议先阅读前面两篇内容再进行本篇内容的学习~
二、取消功能使用方法
xml
<script src="https://cdn.bootcss.com/axios/0.19.0/axios.js"></script>
<script>
let cancel; // 用于保存取消请求的函数
function getProducts() {
axios({
url: 'http://localhost:4000/products1',
cancelToken: new axios.CancelToken((c) => { // c是用于取消当前请求的函数
// 保存取消函数, 用于之后可能需要取消当前请求
cancel = c
})
}).then(
response => {
cancel = null
console.log('请求1成功了', response.data)
},
error => {
cancel = null
console.log('请求1失败了', error.message, error)
}
)
}
getProducts();
setTimeout(() => {
// 取消请求
cancel('取消请求')
}, 1000);
</script>
-
我们首先声明了一个cancel变量
-
然后在给axios 传入的config 配置对象里面添加一个 cancelToken属性 ,我们要给 cancelToken 赋值为什么呢?是axios身上的一个CancelToken构造函数 的实例对象 。这个构造函数接收的是一个回调函数,这个回调函数里面将参数c赋值给了我们的cancel变量 。c是一个函数,cancel现在也变成了函数。在需要取消请求的时候,执行这个cancel函数,就能实现取消请求的功能。
-
那么肯定有反应快的同学会问:axios 什么时候添加了一个CancelToken 的构造函数呀? 其实答案就在我们第一篇分析axios 源码的入口文件
axios/lib/axios.js
里面,只是当时我们只关注了核心逻辑,没有提及这个cancelToken属性。
三、 axios/lib/axios.js
ini
// axios/lib/axios.js
function createInstance(defaultConfig) {...}
// Create the default instance to be exported
// 创建一个axios实例用于导出给用户使用,这个axios实例是一个function
var axios = createInstance(defaults);
// Expose Axios class to allow class inheritance
axios.Axios = Axios;
//
// Factory for creating new instances
axios.create = function create(instanceConfig) {...};
// Expose Cancel & CancelToken
axios.Cancel = require('./cancel/Cancel');
axios.CancelToken = require('./cancel/CancelToken');
axios.isCancel = require('./cancel/isCancel');
// Expose all/spread
axios.all = function all(promises) {
return Promise.all(promises);
};
axios.spread = require('./helpers/spread');
module.exports = axios;
// Allow use of default import syntax in TypeScript
module.exports.default = axios;
四、axios/lib/cancel/CancelToken.js
javascript
function CancelToken(executor) {
if (typeof executor !== 'function') {
throw new TypeError('executor must be a function.');
}
// 为取消请求准备一个promise对象, 并保存resolve函数
var resolvePromise;
// 给实例对象身上添加 promise 属性
this.promise = new Promise(function promiseExecutor(resolve) {
resolvePromise = resolve;
});
// 保存当前token对象
var token = this;
// 立即执行接收的执行器函数, 并传入用于取消请求的cancel函数
executor(function cancel(message) {
// 如果token中有reason了, 说明请求已取消
if (token.reason) {
// Cancellation has already been requested
return;
}
// 将token的reason指定为一个Cancel对象
token.reason = new Cancel(message);
// 将取消请求的promise指定为成功, 值为reason
resolvePromise(token.reason);
});
}
-
CancelToken构造函数 接收的参数叫executor , 首先声明了一个 resolvePromise 变量,再new了一个Promise 实例对象 给到了promise变量 ,这个promise变量的resolve函数 交给了 resolvePromise变量 。
-
继续往下看,执行了executor函数 ,可能看到这里,有些同学不清楚executor是什么?请看我在图中圈出来的红色区域 ,executor就是我们在使用axios时传入的回调函数
(c)=>{ cancel=c; }
-
executor函数 在执行的时候,又传入了一个cancel函数 ,看到这里同学们可能会更蒙。别怕,请再看我在图中圈出来的蓝色区域 ,这个cancel函数 本质上其实就是
(c)=>{ cancel=c; }
中的c 。c 又被赋值给了cancel变量。 -
当计时器
setTimeout(() => { cancel('取消请求') }, 1000)
一秒钟执行cancel函数时,也就是执行蓝色区域的逻辑。 -
当蓝色区域执行时,如果token对象已经有了reason,说明请求已经取消了,就不往下执行。
-
如果没有reason,就给token的reson属性指定为一个Cancel对象 。 最后也是最重要的,执行 resolvePromise 函数,resolvePromise 是promise的成功回调函数,只要resolvePromise函数 执行,promise的状态就会变成fullfilled。
-
那么问题来了?
CancelToken函数的全部逻辑我们都看完了,这怎么就取消了请求??? 定义的promise 难道没作用吗?resolvePromise函数啥时候执行???
没了???
我裤子都脱了,你跟我说结束了
五、xhr.js 与 http.js
好了好了,先把裤子穿上,我们来揭晓答案。这就需要去到/axios/lib/adapters/xhr.js
文件中去找答案, 在第一篇探究请求功能的时候我们没有细看这两个文件的内容, 现在可以打开/axios/lib/adapters/xhr.js
文件。我们在下面截取了163行到178行的代码:
axios/lib/adapters/xhr.js
scss
// axios/lib/adapters/xhr.js
// 如果配置了cancelToken
if (config.cancelToken) {
// 指定用于中断请求的回调函数
config.cancelToken.promise.then(function onCanceled(cancel) {
if (!request) {
return;
}
// 中断请求
request.abort();
// 让请求的promise失败
reject(cancel);
// Clean up request
request = null;
});
}
- 如果传入的配置对象有cancelToken属性 ,cancelToken 这个对象上的promise的then函数就会执行,当promise的状态为成功fullfilled状态 时,onCanceled 函数 会执行。只要执行了 onCanceled函数 中的
request.abort();
就会将请求中断.
如果有同学对XMLHttpRequest对象的abort方法不熟悉的,可以看这个链接: XMLHttpRequest.abort()
-
那么promise状态什么时候才会变成成功状态 ? 同学们赶紧翻到上面的图再看一遍蓝色区域。
-
当定时器过了一秒后是不是执行了
cancel 函数
? -
执行了
cancel函数
是不是相当于执行了右侧蓝色区域 的代码(executor函数
的参数)? -
执行了右侧蓝色区域 的代码是不是
resolvePromise函数
就会执行? -
resolvePromise函数
执行了是不是promise的状态就变成fullfiled成功状态? -
promise变成了成功状态 ,
onCanceled 函数
就会执行,不就取消了请求吗?
真是妙啊,同学们多走几遍上面的流程慢慢捋清楚~
axios/lib/adapters/http.js
同样的,在node.js环境时,axios也是使用了相同的办法来实现取消功能。
我们截取了http.js文件
的部分代码如下:
scss
// axios/lib/adapters/http.js
if (config.cancelToken) {
// Handle cancellation
config.cancelToken.promise.then(function onCanceled(cancel) {
if (req.aborted) return;
req.abort();
reject(cancel);
});
}
如果有同学对Node.js中http的abort方法不熟悉的,可以看这个链接: http.abort()
小结
至此,我们已经知道了axios是如何实现取消请求这一功能,回顾下问题:
❓
axios
的取消请求功能是如何实现的?👉:在使用取消功能时,用户需要给config对象中的cancelToken属性 配置一个CancelToken构造函数的实例对象 ,并且给构造函数传入一个回调函数 ,将参数c函数 保存到cancel 变量里,在需要取消请求的时候,执行cancel函数 。代码:
axios({ cancelToken: new axios.CancelToken((c) => { cancel = c}) })
当用户执行了cancel函数 后,相当于执行了c函数 ,c函数里面会调用resolvePromise函数 ,将实例上的promise状态改成成功fullfilled状态 。在http.js和xhr.js中,一旦promise状态为成功,就会执行onCanceled回调函数 ,在onCanceled回调函 数里面,调用
abort方法
进行取消请求。
补充问题
我们还有一个问题没有解决: ❓ axios.create([config])
返回的实例和axios本身有什么区别?
回到我们的入口文件:/axios/lib/axios.js
ini
// axios/lib/axios.js
function createInstance(defaultConfig) {...}
// Create the default instance to be exported
var axios = createInstance(defaults);
// Expose Axios class to allow class inheritance
axios.Axios = Axios;
+ // Factory for creating new instances
+ axios.create = function create(instanceConfig) {
+ return createInstance(mergeConfig(axios.defaults, instanceConfig));
+ };
// Expose Cancel & CancelToken
axios.Cancel = require('./cancel/Cancel');
axios.CancelToken = require('./cancel/CancelToken');
axios.isCancel = require('./cancel/isCancel');
// Expose all/spread
axios.all = function all(promises) {
return Promise.all(promises);
};
axios.spread = require('./helpers/spread');
module.exports = axios;
// Allow use of default import syntax in TypeScript
module.exports.default = axios;
👉: axios.create()
函数的内部是调用了createInstance
函数得到instance实例对象 ,但是它身上没有isCancel,CancelToken,isCancel,all,spread
等属性。也没有create()
函数和无法支持取消请求功能。
👉: 那作者为什么要设计这个语法?
我们在实际开发中,项目里的请求接口配置不一定都会一样,比如请求A后台服务需要转换response实体,请求B后台服务不需要转换response实体等等,那该怎么办呢?
解决:有了create 这个语法,我们就可以创建多个新 axios, 每个都有自己特有的配置, 分别应用到不同要 求的接口请求中。
最后结语
感谢大家的阅读,如果同学们对文中有任何疑问不理解或者文中有写错的地方可以在评论区指出。 抱拳了~ 掘友们。在这里也提前祝每一位掘友2024年身体健康,万事如意,阖家幸福~