如何解决开发中出现的按钮重复提交的问题?

96 阅读3分钟

解决按钮重复点击问题?

在开发过程中,遇到过这样的场景 点击按钮,发送请求,在请求结束前,一个按钮可以重复点击,导致接口重复请求多次,请求堆积,轻则浪费服务器资源,重则业务逻辑错误、导致数据一致性问题,尤其接口涉及到写入操作(表单提交、订单创建)、用户体验感也下降。因此在网上看了些相关内容,并结合自己粗鄙了解,记录下面试回答的问题,欢迎批评指正

  1. 简单来讲,使用防抖

函数不会被直接执行,而是等触发后等待了设定的时间都没被触发才会被执行。

function debounce(func, delay) {
    let timer = null;
    return function(...args) {
        if(timer) clearTimeout(timer);
        // 重新设置定时器
        timer = setTimeout(() => {
            func.apply(this,args);
            timer = null;
        }, delay);
    };
}
// 使用
const debouncedDubmit = debounce(submit, 300);

限制:如果接口响应时间过长,超过了防抖时间,那么仍可以多次触发请求过程

  1. 使用节流:

节流函数通过记录上次执行时间,确保在设定的时间间隔(如 1 秒)内,无论触发多少次点击,函数只会执行一次。并且在节流期间可以给用户视觉反馈,如按钮置灰、显示加载中loading..

function throttle (fn, interval) {
    let lastTime = 0;
    // 用于标记是否被禁用
    let isDisabled = false;
    return function (...args) {
        const now = Date.now();
        if (now - lastTime >= interval && !isDisabled) {
            isDisabled = true;
            fn.apply(this, args);
            lastTime = now;
            // 间隔时间后恢复可用状态
            setTimeout(() => {
                isDisabled = false;
            }, interval);
        }
    }
}

这里如果要是使用loading的话,可以加一个延时,因为正常来讲,接口1s内都能返回,超过1s才显示loading

  1. 接口请求封装做处理

    每次接口调用,对接口的url进行记录,直到请求结束才将其删除,在每次请求的时候,先检查记录中是否存在这个url请求,有则说明上次请求未完成,禁止请求,同时针对返回较快的接口使用节流,使其固定时间内只能发起1次请求。注意:如果存在同个接口不同参数调用处理。就需要在包含参数了。

其实就是在封装接口处做控制,限制重复提交,并且使用节流优化接口先响应过快的问题

const requestStatusMap = new Map();

/**
 * 封装接口请求,防止重复请求,并对快速返回的接口节流
 * @param {string} key - 请求唯一标识(如url+参数)
 * @param {Function} apiFn - 实际请求函数,返回Promise
 * @param {number} throttleTime - 节流时间(ms),默认300ms
 * @returns {Promise}
 */
function requestWithThrottle(key, apiFn, throttleTime = 300) {
    if (requestStatusMap.has(key)) {
        // 已有请求进行中,直接返回同一个Promise
        return requestStatusMap.get(key);
    }

    // 发起请求
    const promise = new Promise((resolve, reject) => {
        apiFn()
            .then((res) => {
                // 节流:快速返回的接口延迟resolve
                const elapsed = Date.now() - (promise._startTime || Date.now());
                const delay = Math.max(throttleTime - elapsed, 0);
                setTimeout(() => {
                    resolve(res);
                    requestStatusMap.delete(key);
                }, delay);
            })
            .catch((err) => {
                requestStatusMap.delete(key);
                reject(err);
            });
    });

    promise._startTime = Date.now();
    requestStatusMap.set(key, promise);
    return promise;
  1. 前端锁机制

提交时设置一个标志,isSubmitting,提交完成后或失败进行重置

  1. 唯一标识校验

每次提交生成一个唯一Token,提交时候带上,后端进行校验token是否使用过,类似于防重复提交,最佳应该前后端结合使用,后端校验唯一token或者请求幂等性,确保统一数据只被处理一次 7. UI层面解决:

接口请求过程中自动显示遮罩层