接口防止重复调用方案

大家好,今天我向大家介绍对于接口防重复提交的一些方案,请笑纳!

重复调用同个接口导致的问题

  • 表单提交,输入框失焦、按钮点击、值变更提交等容易遇到重复请求的问题,即一次请求还没有执行完毕,用户又点击了一次,这样重复请求可能会造成后台数据异常。又比如在查询数据的时候点击了一次查询,还在处理数据的时候,用户又点击了一次查询。第一次查询执行完毕页面已经有数据展示出来了,用户可能正在看呢,此时第二次查询也处理完返回到前台把页面刷新了,就会造成很不好的体验。

解决方案

  • 1、利用防抖避免重复调用接口
  • 2、采用禁用按钮的方式,loading、置灰等
  • 3、利用axios的cancelToken、AbortController方法取消重复请求
  • 4、利用promise的三个状态改造方法3

方法1:利用防抖

效果:当用户连续点击多次同一个按钮,最后一次点击之后,过小段时间后才发起一次请求

原理:每次调用方法后都产生一个定时器,定时器结束以后再发请求,如果重复调用方法,就取消当前的定时器,创建新的定时器,等结束后再发请求,可以用第三方封装的工具函数例如lodash的debounce方法来简化防抖的代码

javascript 复制代码
<div id="app">    
    <button @click="onClick">请求</button>
</div>

methods: {        
    // 调用lodash的防抖方法debounce,实现连续点击按钮多次,0.3秒后调用1次接口        
    onClick: _.debounce(async function(){            
        let res = await sendPost({username:'zs', age: 20})            
        console.log('请求的结果', res.data)        
    }, 300),
},
// 自定义指令防抖,在directive文件中自定义v-debounce指令
<el-button v-debounce:500="buttonDebounce">按钮</el-button>
  • 优缺点:

      防抖可以有效减少请求的频率,防止接口重复调用,但是如果接口响应比较慢,
      响应时间超过防抖的时间阈值,再次点击也会出现重复请求  需要在触发事件加上防抖处理,不够通用
    

方法2:采用禁用按钮的方式

禁用按钮:在发送请求之前,禁用按钮(利用loading或者disabled属性),直到请求完成后再启用它。这可以防止用户在请求进行中多次点击按钮

javascript 复制代码
<div id="app">    
    <button @click="sendRequest" :loading="loading">请求</button>
 </div>
 
 methods: {    
     async sendRequest() {      
         this.loading = true; // 禁用按钮      
         try {        // 发送请求        
             await yourApiRequestFunction();        // 请求成功后,启用按钮      
         } catch (error) {        // 处理错误情况      } 
         finally {        
             this.loading = false; // 请求完成后,启用按钮      
         }    
     },  
 }
  • 优缺点:

      最有效避免请求还在pending状态时,再次触发事件发起请求  
      不够通用,需要在按钮、tab、输入框等触发事件的地方都加上
    

方法3:利用axios取消接口的api

axios 内部提供的 CancelToken 来取消请求(AxiosV0.22.0版本中把CancelToken打上 👎deprecated 的标记,意味废弃。与此同时,推荐 AbortController 来取而代之)

通过axios请求拦截器,在每次请求前把请求信息和请求的取消方法放到一个map对象当中,并且判断map对象当中是否已经存在该请求信息的请求,如果存在取消上次请求

javascript 复制代码
const pendingRequest = new Map();

function generateReqKey(config) {
    const { method, url, params, data } = config;
    return [method, url, Qs.stringify(params), Qs.stringify(data)].join("&");
}

function addPendingRequest(config) {
    const requestKey = generateReqKey(config);
    config.cancelToken = config.cancelToken || new axios.CancelToken((cancel) => {
        if (!pendingRequest.has(requestKey)) {
            pendingRequest.set(requestKey, cancel);
        }
    });
}

function removePendingRequest(config) {
    const requestKey = generateReqKey(config);
    if (pendingRequest.has(requestKey)) {
        const cancelToken = pendingRequest.get(requestKey);
        cancelToken(requestKey);
        pendingRequest.delete(requestKey);
    }
}

// axios拦截器代码
axios.interceptors.request.use(
    function (config) {
        removePendingRequest(config); // 检查是否存在重复请求,若存在则取消已发的请求
        addPendingRequest(config); // 把当前请求信息添加到pendingRequest对象中
        return config;
    },
    (error) => {
        return Promise.reject(error);`
    }
);
axios.interceptors.response.use(
    (response) => {
        removePendingRequest(response.config); // 从pendingRequest对象中移除请求
        return response;
    },
    (error) => {
        removePendingRequest(error.config || {}); // 从pendingRequest对象中移除请求
        if (axios.isCancel(error)) {
            console.log("已取消的重复请求:" + error.message);
        } else {
            // 添加异常处理
        }
        return Promise.reject(error);
    }
);
  • 优缺点:

      可以防止前端重复响应相同数据导致体验不好的问题  
      但是这个取消请求只是前端取消了响应,取消时请求已经发出去了,后端还是会一一收到所有的请求,该查库的查库,该创建的创建,针对这种情形,服务端的对应接口需要进行幂等控制
    

方法4:利用promise的pending、resolve、reject状态

此方法其实是对cancelToken方案的改造,cancelToken是在请求还在pending状态时,判断接口是否重复,重复则取消请求,但是无法保证服务端是否接收到了请求,我们只要改造这点,在发送请求前判断是否有重复调用,如果用重复调用接口,利用promise的reject拦截请求,在请求resolve或reject状态后清除用来判断是否是重复请求的key

javascript 复制代码
// axios全局拦截文件
import axios from '@/router/interceptors
import Qs from 'qs'

const cancelMap = new Map()

// 生成key用来判断是否是同个请求
function generateReqKey(config = {}) {
    const { method = 'get', url, params, data } = config
    const _params = typeof params === 'string' ? Qs.stringify(JSON.parse(params)) : Qs.stringify(params)
    const _data = typeof data === 'string' ? Qs.stringify(JSON.parse(data)) : Qs.stringify(data)`
    const str = [method, url, _params, _data].join('&')
    return str
}

function checkoutRequest(config) {
    const requestKey = generateReqKey(config)
    // 如果设置允许多次重复请求,直接返回成功,让网络请求继续流通下去
    if (!cancelMap.has(requestKey) || config._allowRepeatRequest) {
        cancelMap.set(requestKey, 0)
        return new Promise((resolve, reject) => {
            axios(config).then(res => {
                resolve(res)
            }).catch(err => {
                reject(err)
            })
        })
    } else { 
      // 如果存在重复请求
        return new Promise((resolve, reject) => {
            reject(new Error())
        })
    }
}

// 移除已响应的请求,移除的时间可设置响应后延迟移除,此时间内可以继续阻止重复请求
export async function removeRequest(config = {}) {
    const time = config._debounceTime || 0
    const requestKey = generateReqKey(config)
    if (cancelMap.has(requestKey)) {
        // 延迟清空,防止快速响应时多次重复调用
        setTimeout(() => {
            cancelMap.delete(requestKey)
        }, time)
    }
}

export default checkoutRequest


// @/router/interceptors 拦截器代码
axios.interceptors.request.use(
    function (config) {
        return config;
    },
    (error) => {
        removeRequest(error.config) // 从cancelMap中移除key
        return Promise.reject(error)
    }
);
axios.interceptors.response.use(
    (response) => {
        removeRequest(response.config) // 从cancelMap中移除key
        return response;
    },
    (error) => {
        removeRequest(error.config || {}) // 从cancelMap中移除key
        return Promise.reject(error);
    }
);

// 接口可以配置_allowRepeatRequest开启允许重复请求
return request({
    url: 'xxxxxxx',
    method: 'post',
    data: data,
    loading: false,
    _allowRepeatRequest: true
 })
  • 优缺点:

      此方法效果跟禁用按钮的效果一致,但是可以全局修改,方案比较通用
    

其他

或者也可以在请求时加个全局loading,但是感觉都不如上一种好

ps

针对可能上传文件使用formData的情况,需要在重复请求那再判断一下

以上是为大家介的四种方法,有更好的建议请评论区留言。

相关推荐
安冬的码畜日常1 小时前
【D3.js in Action 3 精译_029】3.5 给 D3 条形图加注图表标签(上)
开发语言·前端·javascript·信息可视化·数据可视化·d3.js
小白学习日记2 小时前
【复习】HTML常用标签<table>
前端·html
丁总学Java2 小时前
微信小程序-npm支持-如何使用npm包
前端·微信小程序·npm·node.js
yanlele2 小时前
前瞻 - 盘点 ES2025 已经定稿的语法规范
前端·javascript·代码规范
懒羊羊大王呀3 小时前
CSS——属性值计算
前端·css
DOKE3 小时前
VSCode终端:提升命令行使用体验
前端
xgq3 小时前
使用File System Access API 直接读写本地文件
前端·javascript·面试
用户3157476081353 小时前
前端之路-了解原型和原型链
前端
永远不打烊3 小时前
librtmp 原生API做直播推流
前端