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

React异步函数返回undefined Token写入localStorage异常排查

问题描述

编写了一个会在每个页面调用RefreshToken逻辑的React Hook,代码如下:

const refresh = async (refToken) => {
  try {
    if (refToken && !functionalities.isTokenExpired(refToken)) {
      const ntoken = await refreshToken().then((obj) => {
        return obj.token;
      });          
      window.localStorage.setItem("token", ntoken);
    } else {
      window.localStorage.clear();
      toast.error("O seu acesso expirou! Faça novamente o login no sistema.");

      return false;
    }
  } catch (error) {
    toast.error("Não foi possível atualizar o token do usuário");
  }
};

async function useRefreshToken() {
  const refToken = localStorage.getItem("refreshToken") || "";
  if (refToken && !functionalities.isTokenExpired(refToken)) {
    refresh(refToken).then(() => {});
    // setInterval(() => {
    //   refresh(refToken).then();
    // }, 3564000);
  }
}
// 0,99hrs 3564000 - 10min 600000 --
export default useRefreshToken;

代码执行时,总会将localStorage中的token设置为undefined,但服务端实际已经返回了正确的更新后Token,接口返回内容如下:

{"msg":"AUTHORIZATION SUCCESSFUL","token":"eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJ1c2VyVUlEIjoiZmZaTUZTTDF4UGNXdFQ5MDJkd2Z6RFB1WElEMyIsInBvc2l0aW9uIjoibWFuYWdlciIsImNvbXBhbnlJRCI6Ik14Z1ZleEhlSGZzN29ndTJaMm10IiwiaWF0IjoxNjU3MTQyMDU5LCJleHAiOjE2NTcxNDkyNTl9.l9OHl-84S5LrwF1r0fqQl2zuHhS-hrWaFj8QjrxDgQA"}

页面每次加载时至少会调用3次refreshToken方法,无法定位未等待API响应完成就更新Token的原因。
其中封装的refreshToken() API请求方法代码如下:

export const refreshToken = async () => {
  const headers = getDefaultHeaders(true);
  const refToken = localStorage.getItem("refreshToken");
  const body = {
    refreshToken: refToken,
  };
  return await axios.post(`${url}/auth/refreshToken`, body, headers);
};
问题根因
  • 核心错误:Axios响应结构取值错误
    Axios的post方法返回的是完整的响应对象,服务端返回的业务数据统一挂载在响应对象的data属性上,代码直接从响应对象顶层取token字段,拿到的必然是undefined,这就是localStorage存入undefined的直接原因。实际响应结构如下:
    {
      data: { msg: "AUTHORIZATION SUCCESSFUL", token: "真实token值" }, // 服务端返回的业务内容存在这一层
      status: 200,
      statusText: "OK",
      headers: {},
      config: {},
      request: {}
    }
    
  • Hook写法不符合React执行规则,导致重复触发请求
    编写的useRefreshToken没有用useEffect包裹副作用逻辑,只要组件重渲染就会执行一次,页面加载阶段组件多次挂载、重渲染,自然会触发3次甚至更多次refresh请求,和“没等接口返回”无关,是取值路径错误导致的异常。
  • 无并发锁控制,存在竞态隐患
    多个组件同时调用该Hook时,会同时发起多个刷新请求,没有任何防重复处理,不仅浪费接口资源,还可能出现先返回的旧token覆盖后返回的新token的问题。
修复方案
  • 第一步:修正接口返回值的取值路径,直接返回业务层数据
    export const refreshToken = async () => {
      const headers = getDefaultHeaders(true);
      const refToken = localStorage.getItem("refreshToken");
      const body = {
        refreshToken: refToken,
      };
      const res = await axios.post(`${url}/auth/refreshToken`, body, headers);
      // 直接返回服务端业务数据,不需要把整个Axios响应对象往外抛
      return res.data;
    };
    
  • 第二步:改造Hook符合React规范,加防重锁避免重复请求
    import { useEffect, useRef } from 'react';
    
    // 模块级请求锁,跨组件也能防重复刷新
    let isRefreshing = false;
    
    const refresh = async (refToken) => {
      if (isRefreshing) return;
      try {
        if (refToken && !functionalities.isTokenExpired(refToken)) {
          isRefreshing = true;
          const resp = await refreshToken();
          window.localStorage.setItem("token", resp.token);
        } else {
          window.localStorage.clear();
          toast.error("O seu acesso expirou! Faça novamente o login no sistema.");
          return false;
        }
      } catch (error) {
        toast.error("Não foi possível atualizar o token do usuário");
      } finally {
        // 无论请求成功失败都释放锁
        isRefreshing = false;
      }
    };
    
    function useRefreshToken() {
      const hasInit = useRef(false);
      useEffect(() => {
        // 防止Hook多次执行重复触发逻辑
        if (hasInit.current) return;
        hasInit.current = true;
    
        const refToken = localStorage.getItem("refreshToken") || "";
        if (refToken && !functionalities.isTokenExpired(refToken)) {
          refresh(refToken);
          // 开启定时刷新的话一定要加清理函数,避免内存泄漏
          // const timer = setInterval(() => {
          //   // 每次取最新的refreshToken,不要用闭包缓存的旧值
    
          //   const latestRefToken = localStorage.getItem("refreshToken") || "";
          //   refresh(latestRefToken);
          // }, 3564000);
          // return () => clearInterval(timer);
        }
      }, []);
    }
    
    export default useRefreshToken;
    

内容的提问来源于stack exchange,提问作者Daniel Alexandre Oliveira Mode

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.08.27 16:24:34