vlambda博客
学习文章列表

如何取消ajax请求的回调


我们在开发过程中有时候会碰到这样的需求,连续发送多个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、获取CancelTokenvar CancelToken = axios.CancelToken;// 2、生成sourcevar 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来实现。


下面来看个案例,案例页面结构如下:


如何取消ajax请求的回调

点击页面的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请求的回调,并没有说明其如何实现的,下篇文章咱们通过源码看一看这个功能是如何实现的。

如果你有什么意见或者建议欢迎留言。