防止前端页面重复请求

在前端开发中,防止页面重复请求(如重复提交表单、重复调用接口等)是提升用户体验和系统稳定性的关键。以下是 多种防重复请求的方案,涵盖不同场景(表单提交、接口调用、按钮点击等),并提供代码示例和最佳实践。


1. 按钮禁用(适用于表单提交)

实现思路

在表单提交时禁用按钮,防止用户重复点击。

代码示例

html 复制代码
<button id="submitBtn" onclick="submitForm()">提交</button>

<script>
  function submitForm() {
    const btn = document.getElementById('submitBtn');
    btn.disabled = true; // 禁用按钮
    btn.textContent = '提交中...'; // 可选:修改按钮文本

    // 模拟异步请求(如 fetch/axios)
    setTimeout(() => {
      console.log('表单提交成功');
      btn.disabled = false; // 请求完成后恢复按钮
      btn.textContent = '提交';
    }, 2000);
  }
</script>

关键点

  • 简单有效:适用于大多数表单场景。
  • 用户体验:可添加加载状态提示(如按钮文本变化)。
  • 恢复时机:在请求成功或失败后恢复按钮状态。

2. 防抖(Debounce)与节流(Throttle)

适用场景

  • 防抖:连续快速触发时,只执行最后一次(如搜索框输入)。
  • 节流:固定时间间隔内只执行一次(如滚动事件、按钮连续点击)。

代码示例

1. 防抖(Debounce)

javascript 复制代码
function debounce(fn, delay) {
  let timer = null;
  return function(...args) {
    if (timer) clearTimeout(timer);
    timer = setTimeout(() => {
      fn.apply(this, args);
    }, delay);
  };
}

// 使用示例:搜索框输入
const searchInput = document.getElementById('search');
searchInput.addEventListener('input', debounce(() => {
  console.log('触发搜索请求');
}, 500));

2. 节流(Throttle)

javascript 复制代码
function throttle(fn, interval) {
  let lastTime = 0;
  return function(...args) {
    const now = Date.now();
    if (now - lastTime >= interval) {
      fn.apply(this, args);
      lastTime = now;
    }
  };
}

// 使用示例:按钮连续点击
const btn = document.getElementById('btn');
btn.addEventListener('click', throttle(() => {
  console.log('按钮点击');
}, 1000));

关键点

  • 防抖:适合输入框、搜索等延迟触发场景。
  • 节流:适合高频触发事件(如滚动、缩放)。
  • 库支持:可使用 Lodash 的 _.debounce 和 _.throttle。

3. 请求锁(全局状态管理)

实现思路

通过全局变量或状态管理工具(如 Vuex、Redux)标记请求状态,防止重复发起。

代码示例

1. 使用全局变量

javascript 复制代码
let isRequesting = false;

function fetchData() {
  if (isRequesting) return; // 如果正在请求,直接返回
  isRequesting = true; // 标记为请求中

  fetch('/api/data')
    .then(() => console.log('请求成功'))
    .catch(() => console.log('请求失败'))
    .finally(() => {
      isRequesting = false; // 请求完成后重置状态
    });
}

2. 使用 Vuex(Vue 项目)

javascript 复制代码
// store.js
const store = new Vuex.Store({
  state: { isLoading: false },
  mutations: {
    setLoading(state, payload) {
      state.isLoading = payload;
    },
  },
});

// 组件中
methods: {
  fetchData() {
    if (this.$store.state.isLoading) return;
    this.$store.commit('setLoading', true);

    fetch('/api/data')
      .then(() => console.log('请求成功'))
      .catch(() => console.log('请求失败'))
      .finally(() => {
        this.$store.commit('setLoading', false);
      });
  },
},

关键点

  • 全局管理:适合复杂项目,避免重复请求冲突。
  • 状态复用:可结合加载动画、按钮禁用等场景。
  • 扩展性:可记录请求队列(如多个接口依赖)。

4. Token 或请求标识(幂等性设计)

实现思路

为每个请求生成唯一标识(如 UUID),后端通过标识判断是否重复请求。

代码示例

1. 前端生成请求 ID

javascript 复制代码
function generateRequestId() {
  return 'xxxxxxxx-xxxx-4xxx-yxxx-xxxxxxxxxxxx'.replace(/[xy]/g, (c) => {
    const r = (Math.random() * 16) | 0;
    const v = c === 'x' ? r : (r & 0x3) | 0x8;
    return v.toString(16);
  });
}

function fetchData() {
  const requestId = generateRequestId(); // 生成唯一 ID
  const headers = { 'X-Request-ID': requestId };

  fetch('/api/data', { headers })
    .then((res) => res.json())
    .then((data) => console.log(data));
}

2. 后端处理(Node.js 示例)

javascript 复制代码
const requestCache = new Set(); // 简单缓存(实际项目用 Redis)

app.post('/api/data', (req, res) => {
  const requestId = req.headers['x-request-id'];
  if (requestCache.has(requestId)) {
    return res.status(400).json({ error: '重复请求' });
  }
  requestCache.add(requestId); // 记录请求 ID

  // 处理业务逻辑...
});

关键点

  • 幂等性:确保重复请求不会产生副作用(如重复扣款)。
  • 后端支持:需要后端配合实现标识校验。
  • 缓存策略:实际项目可用 Redis 存储请求 ID,设置过期时间。

5. Cancel Token(取消重复请求)

适用场景

使用 axios 或 fetch 时,取消未完成的重复请求。

代码示例

1. Axios 的 CancelToken

javascript 复制代码
let cancelTokenSource = null;

function fetchData() {
  // 取消之前的请求
  if (cancelTokenSource) {
    cancelTokenSource.cancel('取消重复请求');
  }

  cancelTokenSource = axios.CancelToken.source();

  axios.get('/api/data', {
    cancelToken: cancelTokenSource.token,
  })
    .then((res) => console.log(res.data))
    .catch((err) => {
      if (!axios.isCancel(err)) {
        console.log('请求失败:', err);
      }
    });
}

2. Fetch 的 AbortController(现代浏览器支持)

javascript 复制代码
let controller = null;

function fetchData() {
  // 取消之前的请求
  if (controller) {
    controller.abort();
  }

  controller = new AbortController();
  const signal = controller.signal;

  fetch('/api/data', { signal })
    .then((res) => res.json())
    .then((data) => console.log(data))
    .catch((err) => {
      if (err.name !== 'AbortError') {
        console.log('请求失败:', err);
      }
    });
}

关键点

  • 主动取消:适合用户快速切换页面或输入时取消旧请求。
  • 兼容性:AbortController 是现代标准,IE 不支持。
  • 资源释放:避免不必要的网络请求和内存占用。

6. 表单提交的双重验证

实现思路

结合前端验证和后端校验,防止重复提交。

代码示例

1. 前端验证(唯一标识)

html 复制代码
<form id="myForm">
  <input type="hidden" name="formToken" value="<%= generateToken() %>" />
  <!-- 其他表单字段 -->
  <button type="submit">提交</button>
</form>

<script>
  document.getElementById('myForm').addEventListener('submit', (e) => {
    e.preventDefault();
    const btn = e.submitter;
    btn.disabled = true; // 禁用按钮

    const formData = new FormData(e.target);
    fetch('/api/submit', {
      method: 'POST',
      body: formData,
    })
      .then(() => console.log('提交成功'))
      .catch(() => console.log('提交失败'))
      .finally(() => {
        btn.disabled = false;
      });
  });
</script>

2. 后端验证(Node.js 示例)

javascript 复制代码
const submittedTokens = new Set(); // 存储已提交的 Token

app.post('/api/submit', (req, res) => {
  const formToken = req.body.formToken;
  if (submittedTokens.has(formToken)) {
    return res.status(400).json({ error: '重复提交' });
  }
  submittedTokens.add(formToken); // 记录 Token

  // 处理业务逻辑...
});

关键点

  • 前后端结合:前端禁用按钮 + 后端 Token 校验。
  • 安全性:Token 应随机生成且不可预测。
  • 过期处理:Token 可设置过期时间(如 5 分钟)。

7. 最佳实践建议

  1. 表单提交:按钮禁用 + 后端 Token 校验。
  2. 搜索输入:防抖(如 500ms 后触发)。
  3. 连续点击:节流(如 1s 内只允许一次)。
  4. 复杂项目:结合请求锁(Vuex/Redux)和 Cancel Token。
  5. 高并发场景:后端实现幂等性设计(如 Token + 数据库唯一约束)。
相关推荐
luquinn5 小时前
用canvas切图展示及标记在原图片中的位置
开发语言·前端·javascript
巧克力芋泥包5 小时前
前端vue3调取阿里的oss存储
前端
AAA阿giao5 小时前
React Hooks 详解:从 useState 到 useEffect,彻底掌握函数组件的“灵魂”
前端·javascript·react.js
RedHeartWWW5 小时前
Next.js Middleware 极简教程
前端
用户12039112947265 小时前
从零上手 LangChain:用 JavaScript 打造强大 AI 应用的全流程指南
javascript·langchain·llm
饼饼饼5 小时前
从 0 到 1:前端 CI/CD 实战 ( 第一篇: 云服务器环境搭建)
运维·前端·自动化运维
用户47949283569156 小时前
给前端明星开源项目Biome提 PR,被 Snapshot 测试坑了一把
前端·后端·测试
૮・ﻌ・6 小时前
小兔鲜电商项目(一):项目准备、Layout模块、Home模块
前端·javascript·vue
用户47949283569156 小时前
JavaScript 还有第三种注释?--> 竟然合法
javascript