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

React Hook中tokenFn无法捕获token更新返回空值问题求助

解决useToken2 Hook无法获取有效token的问题

问题场景

你编写了用于获取认证token的useToken2 Hook,以及调用它的Test组件,但返回的token始终为空。日志显示useEffect setToken执行,但最终触发tokenFn timeout返回空值。核心原因是status === 'authenticated'在应用启动后一段时间才会被设置,而你用setInterval监听token变化的逻辑因闭包问题无法生效。

原Hook代码

export function useToken2() {
  const { data: session, status } = useSession();
  const [token, setToken] = useState<string | null>(null);

  useEffect(() => {
    if (status === 'authenticated' && session?.accessToken) {
      logger('useEffect setToken')
      setToken(session.accessToken);

    }
  }, [status, session]);

  const tokenFn = useCallback(async (): Promise<string> => {
    return new Promise<string>((resolve) => {
      if (token != null) {
        resolve(token);
      } else {
        const tokenInterval = setInterval(() => {
          if (token != null) {
            clearInterval(tokenInterval);
            resolve(token);
          }
        }, 100);
        setTimeout(() => {
          clearInterval(tokenInterval);
          logger('tokenFn timeout')
          resolve('');
        }, 5000);
      }
    });
  }, [token]);

  return {
    tokenFn,
  };
}

原Test组件代码

const Test: FC = () => {
  const [token, setToken] = useState('');
  const {tokenFn} = useToken2()

  useEffect(() => {
    tokenFn().then((res) => {
      setToken(res)
    })
  }, []);

  return (
      <>
        {token}
      </>
  );
};

问题根因

tokenFn是通过useCallback创建的,它的依赖是token。当token还为null时,tokenFn就被创建,此时它的闭包捕获的是null值的token。后续status变为authenticated后,token被更新,但旧的tokenFn里的token依然是闭包中的旧值,因此setInterval里的判断永远不会成立,最终触发超时返回空。

解决方案

方案一:用useRef保存resolve函数,主动触发

修改useToken2 Hook,使用useRef保存等待中的resolve函数,当token可用时直接调用该函数返回结果:

export function useToken2() {
  const { data: session, status } = useSession();
  const [token, setToken] = useState<string | null>(null);
  // 保存等待中的resolve函数
  const resolveRef = useRef<((value: string) => void) | null>(null);

  useEffect(() => {
    if (status === 'authenticated' && session?.accessToken) {
      logger('useEffect setToken');
      setToken(session.accessToken);
      // 如果有等待的请求,直接返回token
      if (resolveRef.current) {
        resolveRef.current(session.accessToken);
        resolveRef.current = null;
      }
    }
  }, [status, session]);

  const tokenFn = useCallback(async (): Promise<string> => {
    return new Promise<string>((resolve) => {
      if (token != null) {
        resolve(token);
      } else {
        // 记录当前resolve,等待token就绪
        resolveRef.current = resolve;
        // 超时兜底逻辑
        setTimeout(() => {
          // 确保只处理当前的resolve请求
          if (resolveRef.current === resolve) {
            resolveRef.current = null;
            logger('tokenFn timeout');
            resolve('');
          }
        }, 5000);
      }
    });
  }, [token]);

  return {
    tokenFn,
  };
}

方案二:优化Test组件的依赖

同时可以优化Test组件的useEffect依赖,当tokenFn更新时重新调用它,确保获取最新的token:

const Test: FC = () => {
  const [token, setToken] = useState('');
  const { tokenFn } = useToken2();

  useEffect(() => {
    let isMounted = true;
    tokenFn().then((res) => {
      // 防止组件卸载后更新状态
      if (isMounted) {
        setToken(res);
      }
    });
    return () => {
      isMounted = false;
    };
  }, [tokenFn]); // 依赖tokenFn,更新时重新执行

  return (
    <>
      {token}
    </>
  );
};

方案三:去掉冗余state,直接基于session判断

如果不需要额外存token的state,还可以直接基于session和status处理,减少状态冗余:

export function useToken2() {
  const { data: session, status } = useSession();
  const resolveRef = useRef<((value: string) => void) | null>(null);

  useEffect(() => {
    if (status === 'authenticated' && session?.accessToken) {
      logger('useEffect setToken');
      if (resolveRef.current) {
        resolveRef.current(session.accessToken);
        resolveRef.current = null;
      }
    }
  }, [status, session]);

  const tokenFn = useCallback(async (): Promise<string> => {
    return new Promise<string>((resolve) => {
      if (status === 'authenticated' && session?.accessToken) {
        resolve(session.accessToken);
      } else {
        resolveRef.current = resolve;
        setTimeout(() => {
          if (resolveRef.current === resolve) {
            resolveRef.current = null;
            logger('tokenFn timeout');
            resolve('');
          }
        }, 5000);
      }
    });
  }, [status, session]);

  return {
    tokenFn,
  };
}

说明

以上方案都解决了闭包导致的token值无法更新的问题,当token就绪时会主动通知等待的Promise,确保tokenFn能获取到有效token。

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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.07.18 16:05:24