背景
项目当中前端代码会遇到同一个请求向服务器发了多次的情况,我们要避免服务器资源浪费,同一个请求一定时间只允许发一次请求
思路
(1)如果业务简单,例如同一个按钮防止多次点击,我们可以用定时器做防抖处理
(2)如果业务复杂,例如多个组件通过代码,同一个请求发多次,这个时候防抖已经不好处理了,最好是对重复的ajax请求统一做取消操作
方式1-通过定时器做防抖处理
(a)概述
效果:当用户连续点击多次同一个按钮,最后一次点击之后,过小段时间后才发起一次请求
原理:每次调用方法后都产生一个定时器,定时器结束以后再发请求,如果重复调用方法,就取消当前的定时器,创建新的定时器,等结束后再发请求,工作当中可以用第三方封装的工具函数例如lodash
的debounce
方法来简化防抖的代码
(b)代码
<!doctype html> <html lang="en"> <head> <meta charset="utf-8"> <meta http-equiv="x-ua-compatible" content="ie=edge"> <meta name="viewport" content="width=device-width, initial-scale=1.0"> <title>document</title> <script src="https://lf26-cdn-tos.bytecdntp.com/cdn/expire-1-m/lodash.js/4.17.21/lodash.min.js" type="application/javascript"></script> <script src="https://lf26-cdn-tos.bytecdntp.com/cdn/expire-1-m/vue/2.6.14/vue.min.js" type="application/javascript"></script> <script src="https://lf9-cdn-tos.bytecdntp.com/cdn/expire-1-m/axios/0.26.0/axios.min.js" type="application/javascript"></script> </head> <body> <div id="app"> <button @click="onclick">请求</button> </div> </body> <script> // 定义请求接口 function sendpost(data){ return axios({ url: 'https://nodejs-cloud-studio-demo-bkzxs.nodejs-cloud-studio-demo.50185620.cn-hangzhou.fc.devsapp.net/test', method: 'post', data }) } new vue({ el: '#app', methods: { // 调用lodash的防抖方法debounce,实现连续点击按钮多次,0.3秒后调用1次接口 onclick: _.debounce(async function(){ let res = await sendpost({username:'zs', age: 20}) console.log('请求的结果', res.data) }, 300), }, }) </script> </html>
(c)预览
(d)存在的问题
无法解决多个按钮件的重复请求的发送问题,例如下面两种情况
情况-在点击事件上做防抖
按钮事件间是相互独立的,调用的是不同方法,做不到按钮间防抖效果
<!doctype html> <html lang="en"> <head> <meta charset="utf-8"> <meta http-equiv="x-ua-compatible" content="ie=edge"> <meta name="viewport" content="width=device-width, initial-scale=1.0"> <title>document</title> <script src="https://lf26-cdn-tos.bytecdntp.com/cdn/expire-1-m/lodash.js/4.17.21/lodash.min.js" type="application/javascript"></script> <script src="https://lf26-cdn-tos.bytecdntp.com/cdn/expire-1-m/vue/2.6.14/vue.min.js" type="application/javascript"></script> <script src="https://lf9-cdn-tos.bytecdntp.com/cdn/expire-1-m/axios/0.26.0/axios.min.js" type="application/javascript"></script> </head> <body> <div id="app"> <button @click="onclick1" ref="btn1">请求1</button> <button @click="onclick2" ref="btn2">请求2</button> </div> </body> <script> let sendpost = function(data){ return axios({ url: 'http://nodejs-cloud-studio-demo-bkzxs.nodejs-cloud-studio-demo.50185620.cn-hangzhou.fc.devsapp.net/test', method: 'post', data }) } new vue({ el: '#app', mounted() { this.$refs.btn1.click() this.$refs.btn2.click() }, methods: { // 使用lodash对请求方法做防抖 //这里有问题,只是对每个按钮的点击事件单独做了防抖,但是两个按钮之间做不到防抖的效果 onclick1: _.debounce(async function(){ let res = await sendpost({username:'zs', age: 20}) console.log('请求1的结果', res.data) }, 300), onclick2: _.debounce(async function(){ let res = await sendpost({username:'zs', age: 20}) console.log('请求2的结果', res.data) }, 300), }, }) </script> </html>
情况-在接口方法做防抖
按钮间调用的方法是相同的,是可以对方法做防抖处理,但是处理本身对方法做了一次封装,会影响到之前方法的返回值接收,需要对之前的方法做更多处理,变得更加复杂,不推荐
<!doctype html> <html lang="en"> <head> <meta charset="utf-8"> <meta http-equiv="x-ua-compatible" content="ie=edge"> <meta name="viewport" content="width=device-width, initial-scale=1.0"> <title>document</title> <script src="https://lf26-cdn-tos.bytecdntp.com/cdn/expire-1-m/lodash.js/4.17.21/lodash.min.js" type="application/javascript"></script> <script src="https://lf26-cdn-tos.bytecdntp.com/cdn/expire-1-m/vue/2.6.14/vue.min.js" type="application/javascript"></script> <script src="https://lf9-cdn-tos.bytecdntp.com/cdn/expire-1-m/axios/0.26.0/axios.min.js" type="application/javascript"></script> </head> <body> <div id="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内部包裹了一层 return axios({ url: 'http://nodejs-cloud-studio-demo-bkzxs.nodejs-cloud-studio-demo.50185620.cn-hangzhou.fc.devsapp.net/test', method: 'post', data }) }, 300) new vue({ el: '#app', mounted() { this.$refs.btn1.click() this.$refs.btn2.click() }, methods: { onclick1: async function(){ //这里有问题,sendpost返回值不是promise,而是undefined let res = await sendpost({username:'zs', age: 20}) console.log('请求1的结果', res) }, onclick2: async function(){ let res = await sendpost({username:'zs', age: 20}) console.log('请求2的结果', res) }, }, }) </script> </html>
方式2-通过取消ajax请求
(a) 概述
直接对请求方法做处理,通过ajax库的api方法把重复的请求给取消掉
(b)原理
原生ajax取消请求
通过调用xmlhttprequest
对象实例的abort
方法把请求给取消掉
<!doctype html> <html lang="en"> <head> <meta charset="utf-8"> <meta http-equiv="x-ua-compatible" content="ie=edge"> <meta name="viewport" content="width=device-width, initial-scale=1.0"> <title>document</title> </head> <body> </body> <script> //原生ajax的语法 let xhr = new xmlhttprequest(); 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> <html lang="en"> <head> <meta charset="utf-8"> <meta http-equiv="x-ua-compatible" content="ie=edge"> <meta name="viewport" content="width=device-width, initial-scale=1.0"> <title>document</title> <script src="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> <html lang="en"> <head> <meta charset="utf-8"> <meta http-equiv="x-ua-compatible" content="ie=edge"> <meta name="viewport" content="width=device-width, initial-scale=1.0"> <title>document</title> <script src="https://lf26-cdn-tos.bytecdntp.com/cdn/expire-1-m/vue/2.6.14/vue.min.js" type="application/javascript"></script> <script src="https://lf9-cdn-tos.bytecdntp.com/cdn/expire-1-m/axios/0.26.0/axios.min.js" type="application/javascript"></script> <script src="https://lf3-cdn-tos.bytecdntp.com/cdn/expire-1-m/qs/6.10.3/qs.js" type="application/javascript"></script> </head> <body> <div id="app"> <button @click="onclick1" ref="btn1">请求1</button> <button @click="onclick2" ref="btn2">请求2</button> </div> </body> <script> //存储请求信息和取消方法的的map对象 const pendingrequest = new map(); //根据请求的信息(请求方式,url,请求get/post数据),产生map的key function getrequestkey(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的key let 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 => { // 把取消方法添加到map if (!pendingrequest.has(requestkey)) { pendingrequest.set(requestkey, cancel) } }) return config; }, (error) => { return promise.reject(error); } ); let sendpost = function(data){ return axios({ url: 'http://nodejs-cloud-studio-demo-bkzxs.nodejs-cloud-studio-demo.50185620.cn-hangzhou.fc.devsapp.net/test', method: 'post', data }) } new vue({ el: '#app', mounted() { this.$refs.btn1.click() this.$refs.btn2.click() }, methods: { // 使用lodash对请求方法做防抖 //这里有问题,只是对每个按钮的点击事件单独做了防抖,但是两个按钮之间做不到防抖的效果 onclick1: async function(){ let res = await sendpost({username:'zs', age: 20}) console.log('请求1的结果', res.data) }, onclick2: async function(){ let res = await sendpost({username:'zs', age: 20}) console.log('请求2的结果', res.data) }, }, }) </script> </html>
步骤2-通过axios响应拦截器处理请求成功
通过axios
的响应拦截器,在请求成功后在map对象当中,删除该请求信息的数据
<!doctype html> <html lang="en"> <head> <meta charset="utf-8"> <meta http-equiv="x-ua-compatible" content="ie=edge"> <meta name="viewport" content="width=device-width, initial-scale=1.0"> <title>document</title> <script src="https://lf26-cdn-tos.bytecdntp.com/cdn/expire-1-m/vue/2.6.14/vue.min.js" type="application/javascript"></script> <script src="https://lf9-cdn-tos.bytecdntp.com/cdn/expire-1-m/axios/0.26.0/axios.min.js" type="application/javascript"></script> <script src="https://lf3-cdn-tos.bytecdntp.com/cdn/expire-1-m/qs/6.10.3/qs.js" type="application/javascript"></script> </head> <body> <div id="app"> <button @click="onclick1" ref="btn1">请求1</button> <button @click="onclick2" ref="btn2">请求2</button> </div> </body> <script> //存储请求信息和取消方法的的map对象 const pendingrequest = new map(); //根据请求的信息(请求方式,url,请求get/post数据),产生map的key function getrequestkey(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的key let 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 => { // 把取消方法添加到map if (!pendingrequest.has(requestkey)) { pendingrequest.set(requestkey, cancel) } }) return config; }, (error) => { return promise.reject(error); } ); //响应拦截器 axios.interceptors.response.use( (response) => { //请求成功 //删除请求的信息 let requestkey = getrequestkey(response.config) if(pendingrequest.has(requestkey)){ pendingrequest.delete(requestkey) } return response; }, (error) => { return promise.reject(error); } ); let sendpost = function(data){ return axios({ url: 'http://nodejs-cloud-studio-demo-bkzxs.nodejs-cloud-studio-demo.50185620.cn-hangzhou.fc.devsapp.net/test', method: 'post', data }) } new vue({ el: '#app', mounted() { this.$refs.btn1.click() this.$refs.btn2.click() }, methods: { // 使用lodash对请求方法做防抖 //这里有问题,只是对每个按钮的点击事件单独做了防抖,但是两个按钮之间做不到防抖的效果 onclick1: async function(){ let res = await sendpost({username:'zs', age: 20}) console.log('请求1的结果', res.data) }, onclick2: async function(){ let res = await sendpost({username:'zs', age: 20}) console.log('请求2的结果', res.data) }, }, }) </script> </html>
步骤3-通过axios响应拦截器处理请求失败
通过axios
的响应拦截器,在请求失败后在map对象当中,删除该请求信息的数据
<!doctype html> <html lang="en"> <head> <meta charset="utf-8"> <meta http-equiv="x-ua-compatible" content="ie=edge"> <meta name="viewport" content="width=device-width, initial-scale=1.0"> <title>document</title> <script src="https://lf26-cdn-tos.bytecdntp.com/cdn/expire-1-m/vue/2.6.14/vue.min.js" type="application/javascript"></script> <script src="https://lf9-cdn-tos.bytecdntp.com/cdn/expire-1-m/axios/0.26.0/axios.min.js" type="application/javascript"></script> <script src="https://lf3-cdn-tos.bytecdntp.com/cdn/expire-1-m/qs/6.10.3/qs.js" type="application/javascript"></script> </head> <body> <div id="app"> <button @click="onclick1" ref="btn1">请求1</button> <button @click="onclick2" ref="btn2">请求2</button> </div> </body> <script> //存储请求信息和取消方法的的map对象 const pendingrequest = new map(); //根据请求的信息(请求方式,url,请求get/post数据),产生map的key function getrequestkey(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的key let 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 => { // 把取消方法添加到map if (!pendingrequest.has(requestkey)) { pendingrequest.set(requestkey, cancel) } }) return config; }, (error) => { return promise.reject(error); } ); //删除请求信息 function delpendingrequest(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 || {}) } return promise.reject(error); } ); let sendpost = function(data){ return axios({ url: 'http://nodejs-cloud-studio-demo-bkzxs.nodejs-cloud-studio-demo.50185620.cn-hangzhou.fc.devsapp.net/test', method: 'post', data }) } new vue({ el: '#app', mounted() { this.$refs.btn1.click() this.$refs.btn2.click() }, methods: { // 使用lodash对请求方法做防抖 //这里有问题,只是对每个按钮的点击事件单独做了防抖,但是两个按钮之间做不到防抖的效果 onclick1: async function(){ let res = await sendpost({username:'zs', age: 20}) console.log('请求1的结果', res.data) }, onclick2: async function(){ let res = await sendpost({username:'zs', age: 20}) console.log('请求2的结果', res.data) }, }, }) </script> </html>
以上就是vue阻止重复请求实现示例详解的详细内容,更多关于vue阻止重复请求的资料请关注代码网其它相关文章!
发表评论