目录
  • 背景
    • 思路
  • 方式1-通过定时器做防抖处理
    • (a)概述
    • (b)代码
    • (c)预览
    • (d)存在的问题
      • 情况-在点击事件上做防抖
      • 情况-在接口方法做防抖
  • 方式2-通过取消ajax请求
    • (a) 概述
      • (b)原理
      • 原生ajax取消请求
        • axios取消请求
          • (c)代码
            • 步骤1-通过axios请求拦截器取消重复请求
            • 步骤2-通过axios响应拦截器处理请求成功
            • 步骤3-通过axios响应拦截器处理请求失败

        背景

        项目当中前端代码会遇到同一个请求向服务器发了多次的情况,我们要避免服务器资源浪费,同一个请求一定时间只允许发一次请求

        思路

        (1)如果业务简单,例如同一个按钮防止多次点击,我们可以用定时器做防抖处理

        (2)如果业务复杂,例如多个组件通过代码,同一个请求发多次,这个时候防抖已经不好处理了,最好是对重复的ajax请求统一做取消操作

        方式1-通过定时器做防抖处理

        (a)概述

        效果:当用户连续点击多次同一个按钮,最后一次点击之后,过小段时间后才发起一次请求
        原理:每次调用方法后都产生一个定时器,定时器结束以后再发请求,如果重复调用方法,就取消当前的定时器,创建新的定时器,等结束后再发请求,工作当中可以用第三方封装的工具函数例如lodashdebounce方法来简化防抖的代码

        (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取消请求

        通过axiosCancelToken对象实例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阻止重复请求的资料请关注其它相关文章!

        声明:本站所有文章,如无特殊说明或标注,均为本站原创发布。任何个人或组织,在未征得本站同意时,禁止复制、盗用、采集、发布本站内容到任何网站、书籍等各类媒体平台。如若本站内容侵犯了原著者的合法权益,可联系我们进行处理。