【面试题】 vue如何阻止重复请求?

本文涉及的产品
函数计算FC,每月15万CU 3个月
简介: 【面试题】 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>复制代码

大厂面试题分享 面试题库

后端面试题库 (面试必备) 推荐:★★★★★

地址:前端面试题库

相关实践学习
Serverless极速搭建Hexo博客
本场景介绍如何使用阿里云函数计算服务命令行工具快速搭建一个Hexo博客。
相关文章
|
3月前
|
存储 JavaScript
vue页面跳转取消上一个页面请求
本文介绍了在Vue中如何取消上一个页面的请求,以避免页面跳转时请求未完成导致的数据错误。核心方法是使用axios的请求拦截器设置请求的取消令牌(cancelToken),并在vuex中存储这些取消令牌的引用。当进行路由跳转时,通过路由守卫清除这些请求,达到取消上一个页面请求的目的。
183 2
|
4月前
|
JavaScript 前端开发 应用服务中间件
【Vue面试题三十】、vue项目本地开发完成后部署到服务器后报404是什么原因呢?
这篇文章分析了Vue项目在服务器部署后出现404错误的原因,主要是由于history路由模式下服务器缺少对单页应用的支持,并提供了通过修改nginx配置使用`try_files`指令重定向所有请求到`index.html`的解决方案。
【Vue面试题三十】、vue项目本地开发完成后部署到服务器后报404是什么原因呢?
|
4月前
|
JavaScript 前端开发
【Vue面试题二十五】、你了解axios的原理吗?有看过它的源码吗?
这篇文章主要讨论了axios的使用、原理以及源码分析。 文章中首先回顾了axios的基本用法,包括发送请求、请求拦截器和响应拦截器的使用,以及如何取消请求。接着,作者实现了一个简易版的axios,包括构造函数、请求方法、拦截器的实现等。最后,文章对axios的源码进行了分析,包括目录结构、核心文件axios.js的内容,以及axios实例化过程中的配置合并、拦截器的使用等。
【Vue面试题二十五】、你了解axios的原理吗?有看过它的源码吗?
|
4月前
|
JavaScript 前端开发 数据处理
【Vue面试题二十八】、vue要做权限管理该怎么做?如果控制到按钮级别的权限怎么做?
这篇文章讨论了Vue中实现权限管理的策略,包括接口权限、路由权限、菜单权限和按钮权限的控制方法,并提供了不同的实现方案及代码示例,以确保用户只能访问被授权的资源。
【Vue面试题二十八】、vue要做权限管理该怎么做?如果控制到按钮级别的权限怎么做?
|
4月前
|
JavaScript 前端开发
【Vue面试题二十七】、你了解axios的原理吗?有看过它的源码吗?
文章讨论了Vue项目目录结构的设计原则和实践,强调了项目结构清晰的重要性,提出了包括语义一致性、单一入口/出口、就近原则、公共文件的绝对路径引用等原则,并展示了单页面和多页面Vue项目的目录结构示例。
|
3月前
|
缓存 JavaScript 前端开发
vue面试题
vue面试题
173 64
|
2月前
|
JavaScript 前端开发
vue尚品汇商城项目-day01【8.路由跳转与传参相关面试题】
vue尚品汇商城项目-day01【8.路由跳转与传参相关面试题】
43 0
vue尚品汇商城项目-day01【8.路由跳转与传参相关面试题】
|
4月前
|
JavaScript 安全 前端开发
【Vue面试题二十九】、Vue项目中你是如何解决跨域的呢?
这篇文章介绍了Vue项目中解决跨域问题的方法,包括使用CORS设置HTTP头、通过Proxy代理服务器进行请求转发,以及在vue.config.js中配置代理对象的策略。
【Vue面试题二十九】、Vue项目中你是如何解决跨域的呢?
|
4月前
|
JavaScript 前端开发 编译器
【Vue面试题三十二】、vue3有了解过吗?能说说跟vue2的区别吗?
这篇文章介绍了Vue 3相对于Vue 2的改进和新增特性,包括性能提升、体积减小、更易维护、更好的TypeScript支持、新的Composition API、新增的Teleport和createRenderer功能,以及Vue 3中的非兼容性变更和API的移除或重命名。
【Vue面试题三十二】、vue3有了解过吗?能说说跟vue2的区别吗?
|
4月前
|
JavaScript 前端开发 API
【Vue面试题三十一】、你是怎么处理vue项目中的错误的?
这篇文章讨论了Vue项目中错误的处理方式,包括后端接口错误和代码逻辑错误的处理策略。文章详细介绍了如何使用axios的拦截器处理后端接口错误,以及Vue提供的全局错误处理函数`errorHandler`和生命周期钩子`errorCaptured`来处理代码中的逻辑错误。此外,还分析了Vue错误处理的源码,解释了`handleError`、`globalHandleError`、`invokeWithErrorHandling`和`logError`函数的作用和处理流程。
【Vue面试题三十一】、你是怎么处理vue项目中的错误的?