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
相关产品推荐
相关产品推荐

