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

