在平常的开发过程中,你可能会遇到这样一个bug。
测试:我在测一个输入框搜索的功能时,告诉你通过输入框输入的内容,和最终通过输入内容搜索出来的结果对不上。
前端:我是通过调用后端接口拿到的数据,这明显是后端返回的结果有问题啊,找后端去!
后端:通过Postman一通自测后说道,结果没问题啊!找前端去!
前端:我来试试看!一顿输入后发现,没问题啊,这bug我复现不出来啊!
测试:这个bug是偶现的!
前端:一通排查后发现,bug的直接原因是网络问题,根本原因是竞态问题,导致的数据不一致(喜提一个有意思的bug)。
一、bug原因
举个例子:
你先输入1,发起请求A,此时请求参数为{ searchKey: '1' };
你再输入2,发起请求B,此时请求参数为{ searchKey: '12' };
此时由于网络原因,导致先发起的请求A的响应结果比请求B的慢
拿到响应B的结果,此时页面先渲染B的响应结果;
拿到响应A的结果,此时页面再渲染A的响应结果。
所以最后就会发现,你输入的结果,跟搜索出来的结果对不上。
二、解决方案
要解决它,就需要了解一个知识点,那就是如何取消请求。我在发起请求B的时候,把请求B取消不就搞定了么!
三、如何取消请求?
1. 原生XMLHttpRequest
如果用的JavaScript原生的XMLHttpRequest发起的请求,可以通过调用abort方法来取消请求。
var xhr = new XMLHttpRequest();
var url = 'http://localhost:3000/data';
var timer;
xhr.open('GET', url, true);
xhr.onreadystatechange = function() {
if (xhr.readyState === 4 && xhr.status === 200) {
clearTimeout(timer); // 如果在300ms内收到响应,则清除定时器
var response = JSON.parse(xhr.responseText);
console.log(response);
} else {
console.log('Error: ' + xhr.status);
}
};
// 设置定时器,在300ms后取消请求
timer = setTimeout(function() {
xhr.abort();
console.log('Request aborted due to timeout');
}, 300);
// 发起请求
xhr.send();
我这里利用XMLHttpRequest发起了一个请求,如果300ms后未拿到响应,我便会调用abort方法取消请求。
2. fetch
通过fetch发起的请求,需要通过AbortController来实现请求的取消,具体步骤如下:
先通过new AbortController创建一个实例,比如叫controller,
通过controller.signal拿到一个信号,然后再发起fetch请求的时候带上这个信号,然后这个请求就与这个信号关联在一起了。
通过第2步的关联之后,可以随时通过调用controller.abort()取消请求。
具体代码如下,通过fetch发起请求,还是在300ms后未拿到响应便取消请求:
// 创建一个 AbortController 实例
const controller = new AbortController();
const signal = controller.signal;
// 设置取消请求的定时器
const timeout = setTimeout(() => {
controller.abort();
console.log('Request aborted due to timeout');
}, 300); // 300ms
// 发起 Fetch 请求
fetch('http://localhost:3000/data', { signal })
.then(response => {
clearTimeout(timeout); // 清除定时器
return response.json();
})
.then(data => {
console.log(data);
})
.catch(error => {
if (error.name === 'AbortError') {
console.log('Fetch aborted');
} else {
console.error('Error:', error);
}
});
3、axios
其实平常开发中,发请求最常用的方式还是axios,它是对原生XMLHttpRequest的一个封装,让我们用起来更爽。
它是通过CancelToken来取消请求的,其步骤如下:
通过axios上的静态属性CancelToken直接先拿到CancelToken,
调用CancelToken.source()方法,拿到source,
发请求时,通过带上source.token,将请求和source相关联,
通过第2步的关联之后,可以随时通过调用source.cancel取消请求。
完整代码如下:
// 创建一个 CancelToken.source
const CancelToken = axios.CancelToken;
const source = CancelToken.source();
// 发起 Axios 请求并传入 cancel token
axios.get('http://localhost:3000/data', {
cancelToken: source.token
})
.then(response => {
console.log(response.data);
})
.catch(function (thrown) {
if (axios.isCancel(thrown)) {
console.log('Request canceled', thrown.message);
} else {
console.error('Error:', thrown);
}
});
// 在需要的时候取消请求
setTimeout(() => {
source.cancel('Request canceled due to timeout');
}, 300); // 300ms
axios底层也是通过abort方法来实现的。
四、应用场景
那么取消请求有哪些应用场景呢?
连续搜索:那就是上面提到的bug,在连续发起多个请求后,就可能会出现竞态问题,引发bug,
大文件上传:比如在做一个大文件上传功能时,用户上传文件后,页面显示了实时进度条以及取消上传按钮,假如用户此时点击了取消按钮,就应该把已经发送但未拿到响应的请求取消掉。
五、小结
上面主要介绍了通过xhr、fetch以及axios发起的请求如何取消,xhr是通过abort方法来取消请求,fetch则是借助了AbortController类来实现,而axios虽然表面是借助了CancelToken来实现,实际底层还是调用xhr.abort()方法来实现的。
作者:程序员小寒
链接:https://juejin.cn