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

基于Readable Store实现前端API令牌自动刷新的技术问题咨询

使用Readable Store管理认证令牌的问题与解决方案

我正在尝试在Web应用中使用Readable Store作为身份验证令牌。set函数会从API获取认证令牌,然后设置超时,在令牌过期前X秒刷新它,以此实现令牌的持续更新。

// auth.ts
export const ssr = false;

type LoginResponse = {
  access_token: string;
  expires: string;
}

export const authToken: Readable<string> = readable("", (set) => {

  const setAuth = async () => {
    const auth: LoginResponse = await authenticate();
    set(auth.access_token);
    authTimer(auth);
  };

  const authTimer = async (auth: LoginResponse) => {
    const timeRemaining = Math.round(((new Date(auth.expires).getTime() - Date.now())) * 0.9);
    setTimeout(async () => {
      if (auth.access_token === authToken) {
         console.log("refreshing auth token");
         setAuth();
      }
    }, timeRemaining);
  }

  setTimeout(setAuth, 0)
});

遇到的问题与疑问

  • 核心问题:set函数会在后端被调用,导致authenticate()请求失败抛出异常,该函数应仅在前端加载完成后调用,因为请求需要客户端头信息才能成功。
  • 比较问题:无法将Readable Store的值与其他值进行比较(例如auth.access_token === authToken始终返回false);
  • API队列问题:如何将API调用加入队列,仅在authToken不为空字符串时执行?
  • 方案合理性:请问这种方案是否可行,是否符合set函数的设计用途?

解决方案

1. 避免后端执行认证请求

虽然已经声明ssr = false,但仍需确保初始化逻辑仅在浏览器环境触发。修改初始化代码:

// 替换原有的setTimeout(setAuth, 0)
if (typeof window !== 'undefined') {
  setTimeout(setAuth, 0);
}

通过判断window对象是否存在,确保只有前端浏览器环境才会调用authenticate(),避免后端执行时缺少客户端头的问题。

2. 正确比较Store存储的值

authToken是Readable Store实例,并非存储的字符串值,直接比较必然返回false。推荐在Store内部维护当前令牌变量,方便内部比较:

export const authToken: Readable<string> = readable("", (set) => {
  let currentToken = ""; // 内部维护当前令牌值

  const setAuth = async () => {
    const auth: LoginResponse = await authenticate();
    currentToken = auth.access_token;
    set(currentToken);
    authTimer(auth);
  };

  const authTimer = async (auth: LoginResponse) => {
    const timeRemaining = Math.round(((new Date(auth.expires).getTime() - Date.now())) * 0.9);
    setTimeout(async () => {
      // 用内部维护的currentToken进行比较
      if (auth.access_token === currentToken) {
         console.log("refreshing auth token");
         setAuth();
      }
    }, timeRemaining);
  }

  if (typeof window !== 'undefined') {
    setTimeout(setAuth, 0);
  }
});

如果在组件外需要获取值,可通过subscribe方法订阅Store获取当前值,使用后记得取消订阅避免内存泄漏。

3. 实现API调用队列

可以维护一个请求队列,当令牌就绪后批量执行队列中的请求:

// 维护待执行的API请求队列
const apiQueue: Array<() => Promise<void>> = [];
let isTokenReady = false;

// 订阅authToken,监听令牌状态
authToken.subscribe(token => {
  if (token !== "" && !isTokenReady) {
    isTokenReady = true;
    // 依次执行队列中的请求
    while (apiQueue.length > 0) {
      const request = apiQueue.shift();
      request?.();
    }
  } else if (token === "") {
    isTokenReady = false;
  }
});

// 封装队列API调用函数
export const queueApiCall = (fn: () => Promise<void>) => {
  isTokenReady ? fn() : apiQueue.push(fn);
};

// 使用示例
queueApiCall(async () => {
  const response = await fetch('/api/data', {
    headers: {
      'Authorization': `Bearer ${$authToken}`
    }
  });
  // 处理响应逻辑
});

4. 方案可行性与set函数的设计用途

这种方案完全可行,也符合Readable Store的设计初衷。Readable Store的核心就是用于管理需要主动更新的状态,通过初始化回调中的set函数更新状态是标准用法。你通过定时刷新令牌维护认证状态的场景,正是状态管理的典型应用,适配Readable Store的能力。


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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.08.15 13:45:38