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

React中setInterval运行时fetch请求无法送达服务端问题排查

React 倒计时运行时接口请求无法发送问题排查

问题现象

  • 项目包含两类核心逻辑:
    • 倒计时逻辑:调用setInterval()实现每秒秒数减1的4分钟倒计时,计数归零时调用clearInterval()终止计时,同时调用解锁座位的接口
    • 购票逻辑:购票按钮通过onClick绑定事件,触发时向Node.js服务端发送购票接口请求
  • 预期表现:倒计时运行期间点击购票按钮,倒计时正常递减的同时,购票请求可以正常到达服务端
  • 实际表现:点击按钮后无请求到达服务端;已通过Postman验证服务端连通性、接口路由配置正常,通过alert()确认onClick事件可以正常触发绑定函数,初步怀疑和JavaScript单线程机制相关

相关实现代码

倒计时启动逻辑

// 启动计时器的组件逻辑
const startTimer = (sitNumber) => {
  const id = setInterval(async () => {
    updateSec = updateSec - 1;

    console.log("updateSec", updateSec);
    console.log("minutes", time.m);

    if (updateSec === 0) {
      updateMin = updateMin - 1;
      updateSec = 60;
    }

    if (time.m <= 0 || updateMin <= 0) {
      clearInterval(id);
      const response = await fetch(
        "http://localhost:3005/dashboard/unblocksit",
        {
          method: "PUT",
          headers: { "content-type": "application/json" },
          body: JSON.stringify({
            flightName: flight.flightName,
            sit: sitNumber,
          }),
        }
      );
      const parseResponse = await response.json();
      setCount2(count + 1);
      setShowCheckOut(false);
      setTime({ s: 60, m: 4 });
      console.log("this is the unblock response", parseResponse);
    }
    return setTime({ s: updateSec, m: updateMin });
  }, 1000);
};

购票按钮逻辑

// 发送购票请求的组件逻辑
const buyTicket = async (e) => {
  e.preventDefault();
  // alert('button clicked') // 弹窗可以正常触发
  try {
    // 该接口在Postman中可以正常访问
    const response = await fetch("http://localhost:3005/dashboard/buyticket", {
      method: "POST",
      headers: { "content-type": "application/json" },
      body: JSON.stringify({
        flight: flight.flightName,
        sit: sit.sitNumber,
        price: sit.price,
        origin: flight.origin,
        destination: flight.destination,
        passenger,
      }),
    });
    const parseResponse = response.json();
    console.log(parseResponse);
    setCount3(count3 + 1);
  } catch (error) {
    console.log(error);
  }
};

return (
  <div>
    <div className="timer">
      <span>{time.m <= 0 ? time.m : "0" + time.m}</span>
      <span>{time.s <= 0 ? time.s : "0" + time.s}</span>
    </div>
    <div>
      <form>
        <label>Passenger full name</label>
        <input
          type="text"
          value={passenger}
          onChange={(e) => setPassenger(e.target.value)}
        />

        <button onClick={buyTicket}>Purchase</button>
      </form>
    </div>
  </div>
);

服务端购票接口代码

// 服务端buyTicket控制器逻辑
buyTicket: async (req, res) => {
  console.log("hello from buyticket controller"); // 该日志始终未打印
  const flight = req.body.flightName;
  const sit = req.body.sitNumber;
  const price = req.body.price;
  const origin = req.body.origin;
  const detination = req.body.detination;
  const passenger = req.body.passenger;

  const sitUnavailable = await Flight.findOneAndUpdate(
    { flightName: flight },
    {
      $set: { "sits.$[s].isAvailable": false, "sits.$[s].isSuspended": false },
    },
    { arrayFilters: [{ "sits.sitNumber": sit }], new: true }
  );

  return res.send(sitUnavailable);
};

根因分析

问题核心是定时器管理错误导致主线程阻塞:

  1. 倒计时启动逻辑没有做生命周期管控:如果startTimer直接写在组件函数顶层,每次组件因为setTime触发重渲染时,都会新建一个setInterval定时器,旧的定时器没有被清理,会持续驻留
  2. 定时器数量会指数级增长:第1秒1个定时器触发,执行后触发重渲染新增1个定时器;第2秒2个定时器同时触发,两次重渲染新增2个定时器;运行10秒就会有超过500个定时器回调排队执行,主线程被大量同步计算、日志打印、state更新操作完全占满
  3. 受JavaScript单线程机制限制,主线程阻塞时,事件循环无法正常调度网络请求相关任务,即使onClick的同步逻辑(比如alert)可以触发,后续的fetch请求也无法被正常发送

另外代码中还存在几个会导致后续异常的隐性问题:

  • 倒计时的updateSec、updateMin使用组件外普通变量存储,会存在闭包陷阱,拿到的状态值不是最新值
  • setInterval传入async回调时,不会等待内部await执行完成就调度下一次回调,本身就容易出现回调堆积
  • 购票逻辑中response.json()缺少await,无法拿到正常的接口返回值
  • 服务端购票接口字段取值错误:客户端传的是flight字段,服务端错误读取req.body.flightName
  • 时间补零逻辑写反,会导致大于等于10秒/10分的时间显示异常

修复方案

  1. 用useEffect管控定时器生命周期,仅在组件挂载时启动一次定时器,组件卸载时清理定时器,避免重复生成:
useEffect(() => {
  const id = setInterval(() => {
    // 倒计时逻辑
  }, 1000);
  // 清理函数
  return () => clearInterval(id);
}, []);
  1. 倒计时的分、秒值全部用useState管理,不要用外部普通变量,避免闭包问题
  2. 建议用递归setTimeout替代setInterval实现倒计时,避免async回调堆积:
const timerRef = useRef(null);
const tick = useCallback(() => {
  // 执行秒数递减、判断归零逻辑
  timerRef.current = setTimeout(tick, 1000);
}, [/* 相关状态依赖 */])
useEffect(() => {
  timerRef.current = setTimeout(tick, 1000);
  return () => clearTimeout(timerRef.current);
}, [tick])
  1. 给购票逻辑的response.json()加上await,修正服务端接口字段读取错误
  2. 修正时间补零逻辑,小于10的数值才补前导零

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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.08.29 09:39:43