【面试题】 vue如何阻止重复请求?
大厂面试题分享 面试题库
后端面试题库 (面试必备) 推荐:★★★★★
地址:前端面试题库
背景
项目当中前端代码会遇到同一个请求向服务器发了多次的情况,我们要避免服务器资源浪费,同一个请求一定时间只允许发一次请求
思路
(1)如果业务简单,例如同一个按钮防止多次点击,我们可以用定时器做防抖处理
(2)如果业务复杂,例如多个组件通过代码,同一个请求发多次,这个时候防抖已经不好处理了,最好是对重复的ajax请求统一做取消操作
实现
方式1-通过定时器做防抖处理
(a)概述
效果:当用户连续点击多次同一个按钮,最后一次点击之后,过小段时间后才发起一次请求
原理:每次调用方法后都产生一个定时器,定时器结束以后再发请求,如果重复调用方法,就取消当前的定时器,创建新的定时器,等结束后再发请求,工作当中可以用第三方封装的工具函数例如lodash的debounce方法来简化防抖的代码
(b)代码
<!DOCTYPE html><htmllang="en"><head><metacharset="UTF-8"><metahttp-equiv="X-UA-Compatible"content="IE=edge"><metaname="viewport"content="width=device-width, initial-scale=1.0"><title>Document</title><scriptsrc="https://lf26-cdn-tos.bytecdntp.com/cdn/expire-1-M/lodash.js/4.17.21/lodash.min.js"type="application/javascript"></script><scriptsrc="https://lf26-cdn-tos.bytecdntp.com/cdn/expire-1-M/vue/2.6.14/vue.min.js"type="application/javascript"></script><scriptsrc="https://lf9-cdn-tos.bytecdntp.com/cdn/expire-1-M/axios/0.26.0/axios.min.js"type="application/javascript"></script></head><body><divid="app"><button @click="onClick">请求</button></div></body><script>// 定义请求接口functionsendPost(data){
returnaxios({
url: 'https://nodejs-cloud-studio-demo-bkzxs.nodejs-cloud-studio-demo.50185620.cn-hangzhou.fc.devsapp.net/test',
method: 'post',
data
})
}
newVue({
el: '#app',
methods: {
// 调用lodash的防抖方法debounce,实现连续点击按钮多次,0.3秒后调用1次接口onClick: _.debounce(asyncfunction(){
let res = awaitsendPost({username:'zs', age: 20})
console.log('请求的结果', res.data)
}, 300),
},
})
</script></html>复制代码
(c)预览
(d)存在的问题
无法解决多个按钮件的重复请求的发送问题,例如下面两种情况
情况-在点击事件上做防抖
按钮事件间是相互独立的,调用的是不同方法,做不到按钮间防抖效果
<!DOCTYPE html><htmllang="en"><head><metacharset="UTF-8"><metahttp-equiv="X-UA-Compatible"content="IE=edge"><metaname="viewport"content="width=device-width, initial-scale=1.0"><title>Document</title><scriptsrc="https://lf26-cdn-tos.bytecdntp.com/cdn/expire-1-M/lodash.js/4.17.21/lodash.min.js"type="application/javascript"></script><scriptsrc="https://lf26-cdn-tos.bytecdntp.com/cdn/expire-1-M/vue/2.6.14/vue.min.js"type="application/javascript"></script><scriptsrc="https://lf9-cdn-tos.bytecdntp.com/cdn/expire-1-M/axios/0.26.0/axios.min.js"type="application/javascript"></script></head><body><divid="app"><button @click="onClick1"ref="btn1">请求1</button><button @click="onClick2"ref="btn2">请求2</button></div></body><script>let sendPost = function(data){
returnaxios({
url: 'http://nodejs-cloud-studio-demo-bkzxs.nodejs-cloud-studio-demo.50185620.cn-hangzhou.fc.devsapp.net/test',
method: 'post',
data
})
}
newVue({
el: '#app',
mounted() {
this.$refs.btn1.click()
this.$refs.btn2.click()
},
methods: {
// 使用lodash对请求方法做防抖//这里有问题,只是对每个按钮的点击事件单独做了防抖,但是两个按钮之间做不到防抖的效果onClick1: _.debounce(asyncfunction(){
let res = awaitsendPost({username:'zs', age: 20})
console.log('请求1的结果', res.data)
}, 300),
onClick2: _.debounce(asyncfunction(){
let res = awaitsendPost({username:'zs', age: 20})
console.log('请求2的结果', res.data)
}, 300),
},
})
</script></html>复制代码
情况2-在接口方法做防抖
按钮间调用的方法是相同的,是可以对方法做防抖处理,但是处理本身对方法做了一次封装,会影响到之前方法的返回值接收,需要对之前的方法做更多处理,变得更加复杂,不推荐
<!DOCTYPE html><htmllang="en"><head><metacharset="UTF-8"><metahttp-equiv="X-UA-Compatible"content="IE=edge"><metaname="viewport"content="width=device-width, initial-scale=1.0"><title>Document</title><scriptsrc="https://lf26-cdn-tos.bytecdntp.com/cdn/expire-1-M/lodash.js/4.17.21/lodash.min.js"type="application/javascript"></script><scriptsrc="https://lf26-cdn-tos.bytecdntp.com/cdn/expire-1-M/vue/2.6.14/vue.min.js"type="application/javascript"></script><scriptsrc="https://lf9-cdn-tos.bytecdntp.com/cdn/expire-1-M/axios/0.26.0/axios.min.js"type="application/javascript"></script></head><body><divid="app"><button @click="onClick1"ref="btn1">请求1</button><button @click="onClick2"ref="btn2">请求2</button></div></body><script>// 使用lodash对请求方法做防抖, let sendPost = _.debounce(function(data){
//这里有问题,这里的返回值不能作为sendPost方法执行的返回值,因为debounce内部包裹了一层returnaxios({
url: 'http://nodejs-cloud-studio-demo-bkzxs.nodejs-cloud-studio-demo.50185620.cn-hangzhou.fc.devsapp.net/test',
method: 'post',
data
})
}, 300)
newVue({
el: '#app',
mounted() {
this.$refs.btn1.click()
this.$refs.btn2.click()
},
methods: {
onClick1: asyncfunction(){
//这里有问题,sendPost返回值不是promise,而是undefinedlet res = awaitsendPost({username:'zs', age: 20})
console.log('请求1的结果', res)
},
onClick2: asyncfunction(){
let res = awaitsendPost({username:'zs', age: 20})
console.log('请求2的结果', res)
},
},
})
</script></html>复制代码
方式2-通过取消ajax请求
(a) 概述
直接对请求方法做处理,通过ajax库的api方法把重复的请求给取消掉
(b)原理
原生ajax取消请求
通过调用XMLHttpRequest对象实例的abort方法把请求给取消掉
<!DOCTYPE html><htmllang="en"><head><metacharset="UTF-8"><metahttp-equiv="X-UA-Compatible"content="IE=edge"><metaname="viewport"content="width=device-width, initial-scale=1.0"><title>Document</title></head><body></body><script>//原生ajax的语法 let xhr = newXMLHttpRequest();
xhr.open("GET", "http://nodejs-cloud-studio-demo-bkzxs.nodejs-cloud-studio-demo.50185620.cn-hangzhou.fc.devsapp.net/test?username=zs&age=20", true);
xhr.onload = function(){
console.log(xhr.responseText)
}
xhr.send();
//在谷歌浏览器的低速3g下面测试//通过XMLHttpRequest实例的abort方法取消请求setTimeout(() => xhr.abort(), 100);
</script></html>复制代码
axios取消请求
通过axios的CancelToken对象实例cancel方法把请求给取消掉
<!DOCTYPE html><htmllang="en"><head><metacharset="UTF-8"><metahttp-equiv="X-UA-Compatible"content="IE=edge"><metaname="viewport"content="width=device-width, initial-scale=1.0"><title>Document</title><scriptsrc="https://lf9-cdn-tos.bytecdntp.com/cdn/expire-1-M/axios/0.26.0/axios.min.js"type="application/javascript"></script></head><body></body><script>/*axios的取消的语法*/// 方式1-通过axios.CancelToken.source产生cancelToken和cancel方法/*
const source = axios.CancelToken.source();
axios.get('http://nodejs-cloud-studio-demo-bkzxs.nodejs-cloud-studio-demo.50185620.cn-hangzhou.fc.devsapp.net/test', {
params: {username: 'zs', age: 20},
cancelToken: source.token
}).then(res=>{
console.log('res', res.data)
}).catch(err=>{
console.log('err', err)
})
//在谷歌浏览器的低速3g下面测试
//通过调用source的cancel方法取消
setTimeout(() => source.cancel(), 100);
*//**/// 方式2-通过new axios.CancelToken产生cancelToken和cancel方法let cancelFn
const cancelToken = new axios.CancelToken(cancel=>{
cancelFn = cancel
});
axios.get('http://nodejs-cloud-studio-demo-bkzxs.nodejs-cloud-studio-demo.50185620.cn-hangzhou.fc.devsapp.net/test', {
params: {username: 'zs', age: 20},
cancelToken: cancelToken
}).then(res=>{
console.log('res', res.data)
}).catch(err=>{
console.log('err', err)
})
//在谷歌浏览器的低速3g下面测试//通过调用cancelFn方法取消setTimeout(() =>cancelFn(), 100);
</script></html>复制代码
(c)代码
步骤1-通过axios请求拦截器取消重复请求
通过axios请求拦截器,在每次请求前把请求信息和请求的取消方法放到一个map对象当中,并且判断map对象当中是否已经存在该请求信息的请求,如果存在取消上传请求
<!DOCTYPE html><htmllang="en"><head><metacharset="UTF-8"><metahttp-equiv="X-UA-Compatible"content="IE=edge"><metaname="viewport"content="width=device-width, initial-scale=1.0"><title>Document</title><scriptsrc="https://lf26-cdn-tos.bytecdntp.com/cdn/expire-1-M/vue/2.6.14/vue.min.js"type="application/javascript"></script><scriptsrc="https://lf9-cdn-tos.bytecdntp.com/cdn/expire-1-M/axios/0.26.0/axios.min.js"type="application/javascript"></script><scriptsrc="https://lf3-cdn-tos.bytecdntp.com/cdn/expire-1-M/qs/6.10.3/qs.js"type="application/javascript"></script></head><body><divid="app"><button @click="onClick1"ref="btn1">请求1</button><button @click="onClick2"ref="btn2">请求2</button></div></body><script>//存储请求信息和取消方法的的map对象 const pendingRequest = newMap();
//根据请求的信息(请求方式,url,请求get/post数据),产生map的keyfunctiongetRequestKey(config){
const { method, url, params, data } = config;
return [method, url, Qs.stringify(params), Qs.stringify(data)].join("&");
}
//请求拦截器
axios.interceptors.request.use(
function (config) {
//根据请求的信息(请求方式,url,请求get/post数据),产生map的keylet requestKey = getRequestKey(config)
//判断请求是否重复if(pendingRequest.has(requestKey)){
//取消上次请求let cancel = pendingRequest.get(requestKey)
cancel()
//删除请求信息
pendingRequest.delete(requestKey)
}
//把请求信息,添加请求到map当中// 生成取消方法
config.cancelToken = config.cancelToken || new axios.CancelToken(cancel => {
// 把取消方法添加到mapif (!pendingRequest.has(requestKey)) {
pendingRequest.set(requestKey, cancel)
}
})
return config;
},
(error) => {
returnPromise.reject(error);
}
);
let sendPost = function(data){
returnaxios({
url: 'http://nodejs-cloud-studio-demo-bkzxs.nodejs-cloud-studio-demo.50185620.cn-hangzhou.fc.devsapp.net/test',
method: 'post',
data
})
}
newVue({
el: '#app',
mounted() {
this.$refs.btn1.click()
this.$refs.btn2.click()
},
methods: {
// 使用lodash对请求方法做防抖//这里有问题,只是对每个按钮的点击事件单独做了防抖,但是两个按钮之间做不到防抖的效果onClick1: asyncfunction(){
let res = awaitsendPost({username:'zs', age: 20})
console.log('请求1的结果', res.data)
},
onClick2: asyncfunction(){
let res = awaitsendPost({username:'zs', age: 20})
console.log('请求2的结果', res.data)
},
},
})
</script></html>复制代码
步骤2-通过axios响应拦截器处理请求成功
通过axios的响应拦截器,在请求成功后在map对象当中,删除该请求信息的数据
<!DOCTYPE html><htmllang="en"><head><metacharset="UTF-8"><metahttp-equiv="X-UA-Compatible"content="IE=edge"><metaname="viewport"content="width=device-width, initial-scale=1.0"><title>Document</title><scriptsrc="https://lf26-cdn-tos.bytecdntp.com/cdn/expire-1-M/vue/2.6.14/vue.min.js"type="application/javascript"></script><scriptsrc="https://lf9-cdn-tos.bytecdntp.com/cdn/expire-1-M/axios/0.26.0/axios.min.js"type="application/javascript"></script><scriptsrc="https://lf3-cdn-tos.bytecdntp.com/cdn/expire-1-M/qs/6.10.3/qs.js"type="application/javascript"></script></head><body><divid="app"><button @click="onClick1"ref="btn1">请求1</button><button @click="onClick2"ref="btn2">请求2</button></div></body><script>//存储请求信息和取消方法的的map对象 const pendingRequest = newMap();
//根据请求的信息(请求方式,url,请求get/post数据),产生map的keyfunctiongetRequestKey(config){
const { method, url, params, data } = config;
return [method, url, Qs.stringify(params), Qs.stringify(data)].join("&");
}
//请求拦截器
axios.interceptors.request.use(
function (config) {
//根据请求的信息(请求方式,url,请求get/post数据),产生map的keylet requestKey = getRequestKey(config)
//判断请求是否重复if(pendingRequest.has(requestKey)){
//取消上次请求let cancel = pendingRequest.get(requestKey)
cancel()
//删除请求信息
pendingRequest.delete(requestKey)
}
//把请求信息,添加请求到map当中// 生成取消方法
config.cancelToken = config.cancelToken || new axios.CancelToken(cancel => {
// 把取消方法添加到mapif (!pendingRequest.has(requestKey)) {
pendingRequest.set(requestKey, cancel)
}
})
return config;
},
(error) => {
returnPromise.reject(error);
}
);
//响应拦截器
axios.interceptors.response.use(
(response) => {
//请求成功//删除请求的信息let requestKey = getRequestKey(response.config)
if(pendingRequest.has(requestKey)){
pendingRequest.delete(requestKey)
}
return response;
},
(error) => {
returnPromise.reject(error);
}
);
let sendPost = function(data){
returnaxios({
url: 'http://nodejs-cloud-studio-demo-bkzxs.nodejs-cloud-studio-demo.50185620.cn-hangzhou.fc.devsapp.net/test',
method: 'post',
data
})
}
newVue({
el: '#app',
mounted() {
this.$refs.btn1.click()
this.$refs.btn2.click()
},
methods: {
// 使用lodash对请求方法做防抖//这里有问题,只是对每个按钮的点击事件单独做了防抖,但是两个按钮之间做不到防抖的效果onClick1: asyncfunction(){
let res = awaitsendPost({username:'zs', age: 20})
console.log('请求1的结果', res.data)
},
onClick2: asyncfunction(){
let res = awaitsendPost({username:'zs', age: 20})
console.log('请求2的结果', res.data)
},
},
})
</script></html>复制代码
步骤3-通过axios响应拦截器处理请求失败
通过axios的响应拦截器,在请求失败后在map对象当中,删除该请求信息的数据
<!DOCTYPE html><htmllang="en"><head><metacharset="UTF-8"><metahttp-equiv="X-UA-Compatible"content="IE=edge"><metaname="viewport"content="width=device-width, initial-scale=1.0"><title>Document</title><scriptsrc="https://lf26-cdn-tos.bytecdntp.com/cdn/expire-1-M/vue/2.6.14/vue.min.js"type="application/javascript"></script><scriptsrc="https://lf9-cdn-tos.bytecdntp.com/cdn/expire-1-M/axios/0.26.0/axios.min.js"type="application/javascript"></script><scriptsrc="https://lf3-cdn-tos.bytecdntp.com/cdn/expire-1-M/qs/6.10.3/qs.js"type="application/javascript"></script></head><body><divid="app"><button @click="onClick1"ref="btn1">请求1</button><button @click="onClick2"ref="btn2">请求2</button></div></body><script>//存储请求信息和取消方法的的map对象 const pendingRequest = newMap();
//根据请求的信息(请求方式,url,请求get/post数据),产生map的keyfunctiongetRequestKey(config){
const { method, url, params, data } = config;
return [method, url, Qs.stringify(params), Qs.stringify(data)].join("&");
}
//请求拦截器
axios.interceptors.request.use(
function (config) {
//根据请求的信息(请求方式,url,请求get/post数据),产生map的keylet requestKey = getRequestKey(config)
//判断请求是否重复if(pendingRequest.has(requestKey)){
//取消上次请求let cancel = pendingRequest.get(requestKey)
cancel()
//删除请求信息
pendingRequest.delete(requestKey)
}
//把请求信息,添加请求到map当中// 生成取消方法
config.cancelToken = config.cancelToken || new axios.CancelToken(cancel => {
// 把取消方法添加到mapif (!pendingRequest.has(requestKey)) {
pendingRequest.set(requestKey, cancel)
}
})
return config;
},
(error) => {
returnPromise.reject(error);
}
);
//删除请求信息functiondelPendingRequest(config){
let requestKey = getRequestKey(config)
if(pendingRequest.has(requestKey)){
pendingRequest.delete(requestKey)
}
}
//响应拦截器
axios.interceptors.response.use(
(response) => {
//请求成功//删除请求的信息delPendingRequest(response.config)
return response;
},
(error) => {
//请求失败//不是取消请求的错误if (!axios.isCancel(error)){
//服务器报400,500报错,删除请求信息delPendingRequest(error.config || {})
}
returnPromise.reject(error);
}
);
let sendPost = function(data){
returnaxios({
url: 'http://nodejs-cloud-studio-demo-bkzxs.nodejs-cloud-studio-demo.50185620.cn-hangzhou.fc.devsapp.net/test',
method: 'post',
data
})
}
newVue({
el: '#app',
mounted() {
this.$refs.btn1.click()
this.$refs.btn2.click()
},
methods: {
// 使用lodash对请求方法做防抖//这里有问题,只是对每个按钮的点击事件单独做了防抖,但是两个按钮之间做不到防抖的效果onClick1: asyncfunction(){
let res = awaitsendPost({username:'zs', age: 20})
console.log('请求1的结果', res.data)
},
onClick2: asyncfunction(){
let res = awaitsendPost({username:'zs', age: 20})
console.log('请求2的结果', res.data)
},
},
})
</script></html>复制代码
大厂面试题分享 面试题库
后端面试题库 (面试必备) 推荐:★★★★★
地址:前端面试题库
【推荐】国内首个AI IDE,深度理解中文开发场景,立即下载体验Trae
【推荐】编程新体验,更懂你的AI,立即体验豆包MarsCode编程助手
【推荐】抖音旗下AI助手豆包,你的智能百科全书,全免费不限次数
【推荐】轻量又高性能的 SSH 工具 IShell:AI 加持,快人一步
· AI与.NET技术实操系列:基于图像分类模型对图像进行分类
· go语言实现终端里的倒计时
· 如何编写易于单元测试的代码
· 10年+ .NET Coder 心语,封装的思维:从隐藏、稳定开始理解其本质意义
· .NET Core 中如何实现缓存的预热?
· 分享一个免费、快速、无限量使用的满血 DeepSeek R1 模型,支持深度思考和联网搜索!
· 25岁的心里话
· 基于 Docker 搭建 FRP 内网穿透开源项目(很简单哒)
· ollama系列01:轻松3步本地部署deepseek,普通电脑可用
· 按钮权限的设计及实现