自己碰到的问题,扒了很多文档才理清楚,当做是笔记记下来
说到取消接口请求,可能没碰到这样的坑冷不丁还有点懵,为什么会有取消请求这回事,既然决定要请求这个接口了又要取消它,岂不是有点画蛇添足的操作?
1.应用场景,为什么要取消请求
我给你这样一个场景你变能理解为什么需要取消请求这种骚操作了(非常常见的场景,这里是用vue写的)
并且这是一个只有一个dom结构的tab页,也就会说当前显示的是 “通知” 页,“动态”和“系统公告”其实此刻是没有专属的dom结构的,当点击“动态”页的时候“通知”页的数据消息,将获取到的“动态”页的数据渲染到dom上,也就是说,这里的数据都是本地同一个数组盛放的。为什么这么写,因为这里的每个tab的结构都长的很相似,没有必要去写3个dom用来盛放数据,这样代码更简洁。(我为什么要解释这样的东西?emmmm.....)
好的!问题来了!!!当我点击动态栏的时候,这个时候向后台传输一个id=2的参数,通过ajax获取当参数为2的时候的数据,然后在将原本参数为1的tab页的数据清空(默认进来展示的tab1),渲染tab2的页面,我们知道ajax是异步操作的,当接口优化没做好,或者获取的数据过多或者用户网速较差的时候,这个获取数据的ajax操作需要一定的时间来将数据拿到再渲染到页面上,在这个时间段内,如果我再次点击tab3也就是“系统公告”的时候,代码又将id=3的参数传给后台,去获取tab3的数据,而id=2的时候的接口数据或许还在从服务器回到前台的路上,那这个时候,就想从tab1到tab2一样的我们去清空数组中盛放的tab2的数据,清空的是什么?tab2的数据还在返回来的路上还没被push进数组呢!所以这里我们清空了一次空数组!!!那我们就会看到,此时我们页面是处在tab3了,里面的数据因为是被push进去我们本地用来盛放数据的数组的,所以,此时的数据会是id=2的数据 && id=3的数据,这样,问题就暴露出来了!
总感觉自己说了一大堆废话emmmm...那既然问题出来了,我们肯定要解决这个问题的,我们理想的的解决问题的思路应该是 > 现在我得到的数据多余我要的 > 多出来的数据是因为上一个tab页请求延迟造成的 > 那我们就在确定拿到数据的时候再清空数组就好了!
对,没错,ajax resolved中去清除数组中的数据,这样就防止了清除操作执行数据还没请求过来的意外了!
不过因为需求问题,我这里不能这样操作,每个tab页面都是可以上下滚动的,上拉加载更多,如果在resolved中去清除原有数据的话,上来加载更多的时候再下拉就看不到上一页的数据了,被清除了,所以我这里只能另想办法了。。。。
找到一条思路 > 现在我得到的数据多余我要的 > 多出来的数据是因为上一个tab页请求延迟造成的 > 那我就在当前页请求数据的时候掐断上一个页面还没完成的请求就好了
于是开启了扒文档之路。。。
这里就是分析一下接口请求需要被取消时的一些操作(化身超人啦啦啦)
因为我是用vue写的项目,所以标配用的是axios,怎么在axios中取消已经发送的请求呢?
2.在这之前我们还是先介绍一下原生js的abort()这个方法。
直接上代码会比较好一点
<div class="page" id="app">
<button class="get-msg">获取数据</button>
<button class="cancel">取消获取</button>
</div>
<script>
var currentAjax = null
$('.get-msg').click(function () {
currentAjax = $.ajax({
type: 'GET',
url: 'http://jsonplaceholder.typicode.com/comments',
success: function (res) {
console.log(res)
},
error: function (err) {
console.log("获取失败")
}
})
})
$('.cancel').click(function () {
if (currentAjax) {
currentAjax.abort()
}
})
</script>
看这两张效果图就知道,我们的abort()方法起作用了!!!!
3.在axios中取消接口请求操作
好了,接下来才是我们的主题,Axios官方提供了一个取消接口请求的方法,但是怎么用这个方法官网写的很简略(不知道是不是我没找全的问题),反正官网的axios取消接口请求累的半死没看懂,后来是扒了很多大佬的博客,才自己理解出来的
Axios 提供了一个 CancelToken的函数,这是一个构造函数,该函数的作用就是用来取消接口请求的,至于怎么用,看代码吧,我在代码中写了注解
<body>
<div class="page" id="app">
<button @click="getMsg" class="get-msg">获取数据</button>
<button @click="cancelGetMsg" class="cancel">取消获取</button>
<ul>
<li v-for="item in items">{{item.name}}</li>
</ul>
</div>
<script>
var app = new Vue({
el: '#app',
data: {
message: 'Hello Vue!',
items: [],
cancel: null
},
methods: {
getMsg () {
let CancelToken = axios.CancelToken
let self = this
axios.get('http://jsonplaceholder.typicode.com/comments', {
cancelToken: new CancelToken(function executor(c) {
self.cancel = c
console.log(c)
// 这个参数 c 就是CancelToken构造函数里面自带的取消请求的函数,这里把该函数当参数用
})
}).then(res => {
this.items = res.data
}).catch(err => {
console.log(err)
})
//手速够快就不用写这个定时器了,点击取消获取就可以看到效果了
setTimeout(function () {
//只要我们去调用了这个cancel()方法,没有完成请求的接口便会停止请求
self.cancel()
}, 100)
},
//cancelGetMsg 方法跟上面的setTimeout函数是一样的效果,因为手速不够快,哦不,是因为网速太快,导致我来不及点取消获取按钮,数据就获取成功了
cancelGetMsg () {
// 在这里去判断你的id 1 2 3,你默认是展示的tab1,点击的时候不管你上一个请求有没有执行完都去调用这个cancel(),
this.cancel()
}
}
})
</script>
</body>
上两张效果图展示一下:
这样,就完美的解决了我遇到的问题了,点击tab切换的时候,网络敢延迟,我就敢掐掉你的请求,保证我下一个请求不被影响
4.重复点击问题
那我们经常开发的时候会遇到一个重复点击的问题,短时间内多次点击同一个按钮发送请求会加重服务器的负担,消耗浏览器的性能,多以绝大多数的时候我们需要做一个取消重复点击的操作
在vue开发中,这个方法一样完美解决这一问题,通常我们会封装一遍axios,这里我们便可以将此功能封装到拦截器里面去
import axios from 'axios';
axios.defaults.timeout = 5000;
axios.defaults.baseURL ='';
let pending = []; //声明一个数组用于存储每个ajax请求的取消函数和ajax标识
let cancelToken = axios.CancelToken;
let removePending = (ever) => {
for(let p in pending){
if(pending[p].u === ever.url + '&' + ever.method) { //当当前请求在数组中存在时执行函数体
pending[p].f(); //执行取消操作
pending.splice(p, 1); //把这条记录从数组中移除
}
}
}
//http request 拦截器
axios.interceptors.request.use(
config => {
config.data = JSON.stringify(config.data);
config.headers = {
'Content-Type':'application/x-www-form-urlencoded'
}
// ------------------------------------------------------------------------------------
removePending(config); //在一个ajax发送前执行一下取消操作
config.cancelToken = new cancelToken((c)=>{
// 这里的ajax标识我是用请求地址&请求方式拼接的字符串,当然你可以选择其他的一些方式
pending.push({ u: config.url + '&' + config.method, f: c });
});
// -----------------------------------------------------------------------------------------
return config;
},
error => {
return Promise.reject(err);
}
);
//http response 拦截器
axios.interceptors.response.use(
response => {
// ------------------------------------------------------------------------------------------
removePending(res.config); //在一个ajax响应后再执行一下取消操作,把已经完成的请求从pending中移除
// -------------------------------------------------------------------------------------------
if(response.data.errCode ==2){
router.push({
path:"/login",
querry:{redirect:router.currentRoute.fullPath}//从哪个页面跳转
})
}
return response;
},
error => {
return Promise.reject(error)
}
)
这是我自己项目用来封装axios的代码,里面加入了取消重复点击事件的方法(灵感来源于 大田角https://www.jianshu.com/p/4445595488e2),以后写项目一劳永逸啦,妈妈再也不用担心我的代码重复请求的问题啦!!!