You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

React+Express应用单次Axios PATCH请求重复触发问题排查

React+Node.js应用中Axios PATCH请求重复触发的问题排查与解决思路

问题描述

在React(TypeScript)+Node.js/Express应用中,客户端通过Axios发起单次PATCH请求时,服务器会收到两个间隔约1秒的相同请求,导致日志重复、数据库产生重复条目。Postman发起相同请求无此问题,确认问题源于客户端。

客户端代码

SyncWorker组件(简化版)

该组件用于应用加载时同步特定项,已精简至基础形式仍出现重复请求:

import { useCallback, useEffect, useRef } from "react";
import axios from "axios";

function SyncWorker() {
  const processingIdsRef = useRef<Set<string>>(new Set());

  const syncItems = useCallback(async (unsynced: any[]) => {
    for (const item of unsynced) {
      // 改用业务唯一标识ongoing_job_id作为锁键,原代码依赖的item.id不存在,导致锁逻辑失效
      const uniqueId = item.ongoing_job_id;
      if (processingIdsRef.current.has(uniqueId)) continue;

      processingIdsRef.current.add(uniqueId);

      try {
        const response = await axios.patch(
          `${import.meta.env.VITE_PUBLIC_SERVER_BASE_URL}/ongoing-jobs/configure-device`,
          {
            ongoing_job_id: item.ongoing_job_id,
            type: item.type,
            image_url: item.image_url,
            position_identifier: item.position_identifier,
            serial_number: item.serial_number,
          }
        );
        console.log(response.data);
      } catch (err) {
        console.error("Failed to update monitor: ", err);
      } finally {
        processingIdsRef.current.delete(uniqueId);
      }
    }
  }, []);

  useEffect(() => {
    const unsynced: any[] = [
      {
        ongoing_job_id: "ongoing_job_id_424242",
        type: "fan",
        image_url: "www.image-url.com",
        position_identifier: "1.0.0",
        serial_number: "9",
      },
    ];
    syncItems(unsynced).catch(console.error);
    // 可添加日志排查执行次数:console.log("SyncWorker useEffect triggered");
  }, []);

  return null;
}

export default SyncWorker;

App.tsx中组件使用

// App.tsx部分代码
function App() {
  return (
    <>
      <ToastContainer />
      <ContextWrapper>
        <SyncWorker />
        <Router>
          <Routes>
            {/* 路由配置 */}
          </Routes>
        </Router>
      </ContextWrapper>
    </>
  );
}

服务端简化路由处理器

// Node.js/Express路由代码
router.patch("/ongoing-jobs/configure-device", async (req, res) => {
  try {
    const { position_identifier } = req.body;
    console.log("Configuring device for:", position_identifier); // 单次客户端请求触发两次日志
    res.status(200).send("Configuring device for: " + position_identifier);
  } catch (err) {
    console.error("Failed to update job: ", err.message);
    res.status(500).send({ error: err.message });
  }
});

关键观察

  • React Strict Mode已禁用
  • 本地与部署环境均存在该问题
  • Postman测试无重复请求,确认问题源于客户端

排查与解决思路

1. 修复客户端锁逻辑失效问题

原代码中processingIdsRef依赖item.id作为唯一标识,但测试数据中无id字段,导致item.id!为undefined,锁逻辑完全失效。改用ongoing_job_id(业务唯一标识)作为锁的键,确保同一请求不会被重复执行。

2. 排查Axios相关问题

  • 检查Axios配置:确认是否全局注册了重复的请求/响应拦截器,或开启了自动重试逻辑(如第三方retry插件),这类配置可能导致请求被重复发送。
  • 升级Axios版本:某些旧版本Axios存在PATCH请求重复的已知bug,尝试升级至最新稳定版(如axios@1.x)。
  • 查看浏览器Network面板:在开发者工具中检查是否有两个PATCH请求发出,查看请求的Initiator字段,确认请求触发源(如React渲染、第三方脚本等)。

3. 排查React组件挂载/执行逻辑

  • 检查组件是否重复挂载:在SyncWorker的useEffect中添加日志(如console.log("SyncWorker mounted")),确认组件是否被多次挂载。排查ContextWrapper或父组件是否存在重复渲染逻辑。
  • 验证useEffect执行次数:在useEffect内添加日志,确认syncItems是否被调用了两次。即使useEffect依赖为空数组,若组件被重复挂载,也会多次执行。

4. 排除外部干扰

  • 禁用浏览器扩展:部分广告拦截器、代理工具或调试插件可能修改或重复发送请求,禁用所有扩展后测试。
  • 排除CORS预检干扰:PATCH请求会触发OPTIONS预检,但这与重复PATCH请求无关,可忽略。

内容的提问来源于stack exchange,提问作者Tsabary

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.06.26 06:05:23