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

LocalStorage与setInterval更新不一致问题及修复求助

刷新令牌接口调用后LocalStorage更新不一致问题排查

调用刷新令牌(refresh token)接口更新用户认证令牌后,出现LocalStorage更新不一致的情况:

  • 部分场景下LocalStorage能正常更新token字段,应用运行无异常;
  • 但有时接口返回成功响应且无错误日志,token及admin/student字段却被从LocalStorage中删除。

已尝试通过useRef确保refresher函数仅被调用一次,移除Strict Mode后问题仍未解决,以下是相关代码:

refresher函数代码

import { parseTokens, parseAdmin, parseUser } from "../utils/auth-parser";
import { adminAuthFetch } from "../config/axios/axios-admin.config";
import { studentAuthFetch } from "../config/axios/axios-user.config";

export const refresher = async () => {
  const admin = parseAdmin();
  const student = parseUser();
  const token = parseTokens();

  if (!admin && !student) {
    return;
  }

  if (admin && !student) {
    console.log(
      "==========================refreshing token==================",
      new Date().getMilliseconds()
    );
    try {
      const response = await adminAuthFetch.post(`/auth/refresh-tokens`, {
        refresh_token: token.refresh,
      });

      const data = response?.data;
      console.log(data);

      localStorage.setItem(
        "tokens",
        JSON.stringify({
          access: data?.data?.auth_token,
          refresh: data?.data?.refresh_token,
        })
      );
    } catch (error) {
      console.log(error);
      localStorage.removeItem("tokens");
      localStorage.removeItem("admin");
    }
  } else if (student && !admin) {
    console.log(
      "==========================refreshing student token==================",
      new Date().getMilliseconds()
    );
    try {
      const response = await studentAuthFetch.post(`/auth/refresh-tokens`, {
        refresh_token: token.refresh,
      });

      const data = response?.data;
      console.log(data);

      localStorage.setItem(
        "tokens",
        JSON.stringify({
          access: data?.data?.auth_token,
          refresh: data?.data?.refresh_token,
        })
      );
    } catch (error) {
      console.log(error)
      localStorage.removeItem("tokens");
      localStorage.removeItem("student");
    }
  }
};

根组件中调用的Effect代码

const refreshFunction = () => {
    if (!refreshRef.current) {
      refreshRef.current = true;
      refresher();
    } else {
      refreshRef.current = false;
    }
  };

  useEffect(() => {
    const timer = setInterval(refreshFunction, 1000 * 60 * 2);
    return () => clearInterval(timer);
  }, []);

问题分析与解决方案

1. 定时器逻辑缺陷导致并发冲突

当前refreshFunction的交替开关逻辑会导致每隔一次定时器触发就执行一次refresher,但如果前一次异步请求未完成,后一次请求可能读取旧的LocalStorage数据,甚至在失败时误删字段。

修复方案:用ref标记请求状态,确保同一时间只有一个刷新请求在执行:

// 根组件中新增请求状态ref
const isRefreshing = useRef(false);

const refreshFunction = async () => {
  if (isRefreshing.current) return; // 正在刷新则直接返回
  isRefreshing.current = true;
  try {
    await refresher();
  } finally {
    isRefreshing.current = false; // 请求完成后重置状态
  }
};

useEffect(() => {
  const timer = setInterval(refreshFunction, 1000 * 60 * 2);
  return () => clearInterval(timer);
}, []);

2. 响应数据未做有效性校验

直接使用data?.data?.auth_token这类可选链取值,如果接口返回的data.data为空或字段名不符,会导致存入LocalStorage的token是undefined,后续逻辑可能因读取无效token触发删除操作。

修复方案:更新LocalStorage前强制校验必要字段:

// 在refresher函数的try块中添加校验
const data = response?.data;
console.log(data);

// 校验token字段是否存在且有效
if (!data?.data?.auth_token || !data?.data?.refresh_token) {
  console.error("刷新令牌接口返回无效数据");
  throw new Error("Invalid token data from refresh API");
}

localStorage.setItem(
  "tokens",
  JSON.stringify({
    access: data.data.auth_token,
    refresh: data.data.refresh_token,
  })
);

3. 未处理请求竞态问题

两次刷新请求几乎同时发起时,旧请求的响应可能覆盖新请求的结果,或旧请求失败时误删新请求刚更新的字段。

修复方案:给每个请求添加唯一标识,忽略过期请求的响应:

export const refresher = async () => {
  const requestTimestamp = Date.now(); // 生成请求唯一标识
  const admin = parseAdmin();
  const student = parseUser();
  const token = parseTokens();

  if (!admin && !student) {
    return;
  }

  if (admin && !student) {
    console.log("==========================refreshing token==================", requestTimestamp);
    try {
      const response = await adminAuthFetch.post(`/auth/refresh-tokens`, {
        refresh_token: token.refresh,
      });

      // 如果当前请求已过期,忽略响应
      if (Date.now() - requestTimestamp > 1000) return;

      const data = response?.data;
      if (!data?.data?.auth_token || !data?.data?.refresh_token) {
        throw new Error("Invalid token data");
      }

      localStorage.setItem(
        "tokens",
        JSON.stringify({
          access: data.data.auth_token,
          refresh: data.data.refresh_token,
        })
      );
    } catch (error) {
      console.log(error);
      // 删除前校验当前用户状态仍存在,避免误删
      const currentAdmin = parseAdmin();
      if (currentAdmin) {
        localStorage.removeItem("tokens");
        localStorage.removeItem("admin");
      }
    }
  } 
  // student分支做相同修改
};

4. catch块未区分错误类型

当前所有错误都会删除LocalStorage字段,但网络波动、超时等临时错误不需要删除用户状态,会影响体验。

修复方案:仅在令牌失效(如401/403状态码)时删除状态:

catch (error) {
  console.log(error);
  // 判断是否为令牌失效类错误
  if (error.response?.status === 401 || error.response?.status === 403) {
    localStorage.removeItem("tokens");
    localStorage.removeItem("admin"); // student分支替换为student
  } else {
    console.error("刷新令牌失败,非令牌失效:", error);
    // 可选择重试或忽略,不删除用户状态
  }
}

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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.08.05 21:10:59