axios核心源码逻辑实现(四)
前言
大家好,欢迎来到今天的axios的源码学习。在上一个篇章中我们完成了拦截器功能的相关实现。今天我们要完成的目标是在发送请求之后如何取消请求,并能写出相关的源码逻辑。
目标
可以看到当我们点击蓝色按钮向本地服务器发送请求后,快速点击黄色按钮取消了这次请求
实现步骤
-
创建JSON文件,开启服务器
-
创建Axios构造函数
-
在Axios原型上挂载request方法,内部完成相关业务逻辑
-
创建dispatchRequest函数
-
创建xhrAdaptert函数并发送请求
-
创建axios()函数
-
创建CancelToken函数
-
点击按钮发送并取消请求
具体功能实现
书写功能测试代码
html文本部分
引入以下样式用来美化按钮
<link crossorigin='anonymous' href="https://cdn.bootcss.com/twitter-bootstrap/3.3.7/css/bootstrap.min.css" rel="stylesheet">
按钮
<div class="container">
<h2 class="page-header">axios取消请求</h2>
<button class="btn btn-primary"> 发送请求 </button>
<button class="btn btn-warning"> 取消请求 </button>
</div>
script模块基本代码
//获取按钮 以上为模拟实现的代码
const btns = document.querySelectorAll('button');
//2.声明全局变量
let cancel = null;
//发送请求
btns[0].addEventListener('click', function () {
//检测上一次的请求是否已经完成
if (cancel !== null) {
//取消上一次的请求
cancel();
}
//创建 cancelToken 的值
let cancelToken = new CancelToken(
function (c) {
cancel = c;
}
);
axios({
method: 'GET',
url: 'http://localhost:3000/posts',
//1. 添加配置对象的属性
cancelToken: cancelToken
}).then(response => {
console.log(response);
//将 cancel 的值初始化
cancel = null;
})
})
//绑定第二个事件取消请求
btns[1].addEventListener('click', function () {
cancel();
})
可以看到在script模块中我们获取了两个按钮,当点击第一个按钮我们发送请求,其内部的回调函数中创建了一个cancelToken对象,并将这个cancelToken放置在了axios请求的内部作为参数。而点击第二个按钮我们只是执行了一个cancel函数,当然现在里面的很多函数和对象还不存在,下面我们将逐渐完善。
创建JSON文件,开启服务器
此过程与前文一致,在此不做赘述
创建Axios构造函数
// Axios构造函数
function Axios(config) {
this.config = config;
}
挂载request方法
Axios.prototype.request = function (config) {
return dispatchRequest(config);
}
可以看到我们在Axios的原型上挂载了一个request函数,内部返回了dispatchRequest函数,并接受上面传递下来的参数参数config
创建dispatchRequest函数
//dispatchRequest 函数
function dispatchRequest(config) {
return xhrAdapter(config);
}
这一步中我们创建了dispatchRequest函数,其内部返回xhrAdapter函数,并接受上面传递下来的参数参数config。
创建xhrAdapter函数
xhrAdapter函数的返回值必须是一个promise类型的值,并要在调用原生内置的XMLHttpRequest的实例化对象完成发送请求,此操作在前文中书写并解释过,在此不做过多阐述
function xhrAdapter(config) {
//发送 AJAX 请求
return new Promise((resolve, reject) => {
//实例化对象
const xhr = new XMLHttpRequest();
//初始化
xhr.open(config.method, config.url);
//发送
xhr.send();
//处理结果
xhr.onreadystatechange = function () {
if (xhr.readyState === 4) {
//判断结果
if (xhr.status >= 200 && xhr.status < 300) {
//设置为成功的状态
resolve({
status: xhr.status,
statusText: xhr.statusText
});
} else {
reject(new Error('请求失败'));
}
}
}
})
}
可以看到xhrAdapter函数主要作用之一就是发送请求,并返回一个promise类型的值,最终返回给request,也就是axios执行的结果
创建axios函数
//创建 axios 函数
const context = new Axios({});
const axios = Axios.prototype.request.bind(context);
此步操作在前面的文章中都有涉及并讲解,在此不做赘述
创建CancelTok函数
这一步操作是我们今天要理解并实现的核心代码。由于我们在功能测试代码中的axios( { ... })传入了cancelToken参数。而这个cancelToken是通过new CancelToken构造函数而来的,因此我们现在要创建一个CancelToken函数,其接收一个参数executor
在CancelTok函数内部我们要申明一个变量resolvePromise供我们后期使用。其次我们要申明一个变量promise,接收来自原生内置Promise函数实例化对象的结果
可以看到在new Promise的内部,我们调用里面的函数,其执行结果是把resolve参数赋值给了刚刚申明的变量resolvePromise
注意:此时参数resolve是一个函数,可以使得这个变量变为一个成功的promise函数
关于executor : 当我们new CancelToken之后,它会往CancelToken函数中传入一个参数,这个参数就是一个函数,如图
因此我们把一个函数作为参数传入给了这个excutor函数,如图
这一步中,我们执行了executor函数,执行的结果就是将这个传递过来的函数赋值给了cancel
一定要注意此时只是executor函数执行,而传递过来的function函数并未执行
代码
//CancelToken 构造函数
function CancelToken(executor) {
//声明一个变量
var resolvePromise;
//为实例对象添加属性
this.promise = new Promise((resolve) => {
//将 resolve 赋值给 resolvePromise
resolvePromise = resolve
});
//调用 executor 函数
executor(
function () {
resolvePromise();
});
}
完善xhrAdapter函数
接下来我们要完成xhrAdapter函数,当发送请求后我们如果想快速终止这次请求,其核心是调用xhr身上的abort( )函数
在进入到这一步之前,我们要判断config配置信息中是否携带有cancelToken这个对象,如果有,再往下继续;如果没有,直接不进行此步操作。
可以看到,在这个处理中我们会检查config.cancelToken.promise的状态是否为成功,如果成功则直接调用abort( )函数,终止请求,如果携带了cancelToken参数而未调用,则promise的值为pending不执行then内部的代码
而如何终止请求发送呢?来看下面
终止请求流程理解
上面我们提到过cancel是一个函数,而如果这个函数一执行,其内部的resolvePromise() 就会执行,而这个函数一执行,就相当于上面的resolve函数执行,那变量promise就必然为一个成功的promise,而此时 config.cancelToken.promise就为一个成功的promise函数,所以then方法就会执行内部的第一个函数,终止请求,可谓牵一发而动全身!
那我们什么时候来执行这个cancel函数呢?那这个执行权当然要交付给我们的用户,因此第二个按钮事件中的回调执行结果就是就是cancel(),点击按钮取消请求
到此为止我们的逻辑代码全部书写完毕,这里要理解几个核心点
- 函数传参的过程,以及不同函数作为参数时的状态
- 终止请求的方法是借助了xhr身上的abort( ) 函数
- cancel函数的执行,会起连锁反应,影响变量promise的状态
各位在执行时务必要把网速调至为低速3g,如图
这样执行axios时会执行的稍微慢一些,有充分的时间来点击按钮取消请求查看效果
总结
至此我们完成了所有关于axios核心功能的逻辑实现,分别是
- axios发送请求的测试实现
- axios发送请求本地服务器实现
- axios拦截器的相关实现
- axios请求取消的实现
关于axios的源码理解与逻辑实现到此告一段落,谢谢大家
本节代码
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title> axios 的由来</title>
</head>
<body>
<script>
// console.log(axios);
// axios();
// axios.get();
// axios.post();
//构造函数
function Axios(config) {
//初始化
this.defaults = config;//为了创建 default 默认属性
this.intercepters = {
request: {},
response: {}
}
}
//原型添加相关的方法
Axios.prototype.request = function (config) {
console.log('发送 AJAX 请求 请求的类型为 ' + config.method);
}
Axios.prototype.get = function (config) {
return this.request({ method: 'GET' });
}
Axios.prototype.post = function (config) {
return this.request({ method: 'POST' });
}
//声明函数
function createInstance(config) {
//实例化一个对象
let context = new Axios(config);// context.get() context.post() 但是不能当做函数使用 context() X
//创建请求函数
let instance = Axios.prototype.request.bind(context);// instance 是一个函数 并且可以 instance({}) 此时 instance 不能 instance.get X
//将 Axios.prototype 对象中的方法添加到instance函数对象中
Object.keys(Axios.prototype).forEach(key => {
instance[key] = Axios.prototype[key].bind(context);// this.default this.interceptors
});
//为 instance 函数对象添加属性 default 与 interceptors
Object.keys(context).forEach(key => {
instance[key] = context[key];
});
// console.dir(instance)
return instance;
}
let axios = createInstance();
//发送请求
axios({method:'POST'});
axios.get({});
axios.post({});
</script>
</body>
</html>