我们在开发过程中有时候会碰到这样的需求,连续发送多个ajax请求,请求个数大于等于2,后面的ajax请求发送时,如果前面的ajax请求还没有返回,就取消前面ajax请求回调的执行。
在继续后面的内容之前,先同步一个概念,文中所说的取消ajax的请求,指的是取消ajax请求的回调函数,ajax的请求发送后,这个请求我们是阻止不了的,但是可以取消其回调的执行。
举个简单的例子,你泼了一盆水,水已经泼出去了,水离开盆之后是阻止不了的,但是可以阻止泼出去的后果,比方说你泼了产品经理一盆水,赶紧跑就不会被打到。
接下来,看一下原生js如何处理ajax请求的取消,原生js利用的是XMLhttprequest实例的一个叫做abort的方法,看一下官方文档的代码:
var xhr = new XMLHttpRequest(),
method = "GET",
url = "/";
xhr.onreadystatechange = () => {
if(xhr.readyState===4&&xhr.status===200){
console.log('ok')
}
}
xhr.open(method, url, true);
xhr.send();
xhr.abort();
我们在浏览器中调试代码,在调用abort之后,onreadystatechange会被执行,但是满足readystate=4和status=200的情况就不会出现了。
官方文档提到,xhr调用abort之后,readyState 会被重置为0,readyState变化会触发onreadystatechange函数,而readyState已经被重置为0,此时用户定义的回调函数就不会执行了。
我个人感觉不同的浏览器实现机制可能不一样。我们需要了解的是,ajax请求发送后,在回调调用之前,调用abort,这个ajax的回调就不会被执行了。
以上便是原生js如何处理取消ajax请求回调的原理了。
下面看一下在使用axios过程中如何取消ajax的回调,axios终止请求的用法很简单,代码示例如下:
const axios = require('axios')
// 1、获取CancelToken
var CancelToken = axios.CancelToken;
// 2、生成source
var source = CancelToken.source();
console.log(source.token)
axios.get('/user/12345', {//get请求在第二个参数
// 3、注入source.token
cancelToken: source.token
}).catch(function (thrown) {
console.log(thrown)
});
axios.post('/user/12345', {//post请求在第三个参数
name: 'new name'
}, {
cancelToken: source.token
}).catch(e=>{
console.log(e)
});
// 4、调用source.cancel("原因"),终止注入了source.token的请求
source.cancel('不想请求了');
仔细阅读源码,假如我们要取消axios请求的回调,我们需要调用axios.CancelToken.source方法,得到一个source对象,这个对象有两个属性,一个是token,一个是cancel,token传递到需要被取消请求回调的参数中,cancel是一个方法,调用cancel会取消传递了token的ajax请求。
有哪些场景会用到这个功能呢,假如页面中有个一按钮,每次点击按钮,都会发送异步请求,用户手速快,多次点击,就会发送多次请求,如果我们不做限制,连续点击n次那么页面就会发送n次请求,其回调都会执行,我们需要用户点击第n次请求时,前面的请求中未及时返回的请求会被取消掉,这时就会用到abort方法了。
还有就是在React或者Vue项目中,当我们从PageA切换都PageB的时候,由于PageA页面中请求还没有响应,页面已经切换到PageB了,此时需要取消PageA中的请求的回调。凡此种种都可以用abort来实现。
下面来看个案例,案例页面结构如下:
点击页面的click按钮,ajax请求回调函数的作用是修改当前组件中state的arr属性,代码如下:
class Three extends Component {
constructor(props) {
super(props)
this.state = {
arr: ''
}
}
click = () => {
axios.get("https://cnodejs.org/api/v1/topics").then(data => {
this.setState({
arr: data.data,
})
}).catch(e => {
console.log(e)
})
}
render() {
let {arr} = this.state;
return <div>
<p>{arr.length} </p>
<button onClick={this.click}>Click</button>
<Link to={'/admin/list/clock'}>首页</Link>
</div>
}
}
点击click按钮,但是在请求未返回时,我们通过导航切换到其他路由,此时浏览器就会出现警报,如图:
警报的原因是当前页面渲染的组件已经不是发出请求的组件,而异步的回调还试图去修改上一个组件的状态,此时就会发出警告了。
此时的回调中还保存着上一个组件的状态,形成了一个闭包,如何解决呢?警报中已经给出了提示,cancel all subscriptions and asynchronous tasks in then componentWillUnmount method,啥意思呢?就是在componentWillUnmount函数中取消所有订阅的任务和异步任务,如何做呢,代码如下:
class Two extends Component{
constructor(props){
super(props)
// 1、调用axios.CancelToken.source()方法生成source实例;
var CancelToken = axios.CancelToken;
var source = CancelToken.source();
this.state = {
source,
arr:''
}
}
click=()=> {
let { token } = this.state.source
// 2、将source.token以cancelToken参数形式传入axios的请求中;
axios.get("https://cnodejs.org/api/v1/topics", {
cancelToken: token
}).then(data => {
this.setState({
arr:data,
loading:'加载完成'
})
console.log(data);
}).catch(e => {
console.log(e)
})
}
componentWillUnmount(){
// 3、在组件即将卸载时取消当前组件的所有异步任务
const { cancel } = this.state.source;
cancel("销毁了")
}
render(){
let {a} = this.state
return <div>
<p>{} has clicked <strong>{a}</strong> Times </p>
<button onClick={this.click}>Click</button>
<Link to={'/admin/list/clock'}>首页</Link>
</div>
}
}
阅读源码,首先生成source实例,然后将source的token传入请求函数中,最后在组件即将卸载时调用cancel方法。此时再进行上面的操作就不会出现报警提示了。
上面演示的是class组件,如果是function组件,代码如何写呢,如下:
const Index = function (){
let [arr,setArr] = useState('');
var CancelToken = axios.CancelToken;
var source = CancelToken.source();
let Click = ()=>{
axios.get("https://cnodejs.org/api/v1/topics", {
cancelToken: source.token
}).then(data => {
setArr(data.data.data);
console.log("2222");
}).catch(e => {
console.log(e)
})
}
useEffect(()=>{
return ()=>{
console.log("quxiaole")
source.cancel()
}
},[])
return <div>
<p>{arr.length} </p>
<button onClick={Click}>Click</button>
<Link to={'/admin/list/clock'}>首页</Link>
</div>
}
在函数组件中我们做了同样的事情,大家可以自己测试一下。
现在通常不论是class组件还是函数组件,这种用法都不太常见了,现在一般把数据维护在redux之类的状态容器中,使用状态容器维护数据是不会出现warning警报的,因为数据容器将所有数据维护在了全局作用域,组件在销毁重建过程中修改的都是全局状态下的数据,不存在闭包的情况。
文章到此就要结束了,总结一下:
1.首先介绍了原生js是如何取消ajax请求的,本质是通过调用abort函数将readyState重置为0。
2.然后我们介绍了哪些场景会用到取消ajax请求的功能。
3.最后我们用一个React的案例结合axios,演示使用axios如何取消ajax请求。
本篇文章只演示了在使用axios时如何取消ajax请求的回调,并没有说明其如何实现的,下篇文章咱们通过源码看一看这个功能是如何实现的。