Chrome扩展Manifest V3:Service Worker中避免refreshToken重复调用
解决Service Worker中refreshToken并发调用问题
多个API请求同时触发token过期时,会导致refreshToken()被并发调用,进而出现接口返回null的问题。我们可以通过缓存刷新请求的Promise来避免重复调用,让所有等待刷新的请求共用同一个结果。
修改后的实现代码:
// 全局维护一个正在进行的刷新token的Promise let refreshTokenPromise = null; exampleApi2(); exampleApi3(); exampleApi4(); async function exampleApi1() { // 实际API调用逻辑(携带当前token) const response = await fetch('/api/xxx', { headers: { Authorization: `Bearer ${getCurrentToken()}` } }); // 判断refresh token过期(根据实际业务调整判断逻辑) if (response.status === 401 && await response.text() === 'refreshtoken expired') { await refreshToken(); // 刷新成功后重试API return exampleApi1(); } return response.json(); } async function exampleApi2() { // 逻辑同exampleApi1 const response = await fetch('/api/yyy', { headers: { Authorization: `Bearer ${getCurrentToken()}` } }); if (response.status === 401 && await response.text() === 'refreshtoken expired') { await refreshToken(); return exampleApi2(); } return response.json(); } async function exampleApi3() { // 逻辑同exampleApi1 const response = await fetch('/api/zzz', { headers: { Authorization: `Bearer ${getCurrentToken()}` } }); if (response.status === 401 && await response.text() === 'refreshtoken expired') { await refreshToken(); return exampleApi3(); } return response.json(); } async function exampleApi4() { // 逻辑同exampleApi1 const response = await fetch('/api/aaa', { headers: { Authorization: `Bearer ${getCurrentToken()}` } }); if (response.status === 401 && await response.text() === 'refreshtoken expired') { await refreshToken(); return exampleApi4(); } return response.json(); } async function refreshToken() { // 如果已有正在进行的刷新请求,直接复用这个Promise if (refreshTokenPromise) { return refreshTokenPromise; } try { // 创建刷新请求的Promise并绑定到全局变量 refreshTokenPromise = (async () => { // 调用实际的刷新token接口 const refreshResponse = await fetch('/api/refresh-token', { method: 'POST', body: JSON.stringify({ refreshToken: getCurrentRefreshToken() }) }); if (!refreshResponse.ok) { throw new Error('刷新token失败'); } const newTokens = await refreshResponse.json(); // 将新的token保存到Service Worker缓存或IndexedDB saveTokens(newTokens.accessToken, newTokens.refreshToken); return newTokens; })(); // 给Promise绑定finally,请求完成后清空全局变量 refreshTokenPromise = refreshTokenPromise.finally(() => { refreshTokenPromise = null; }); return await refreshTokenPromise; } catch (error) { // 刷新失败时抛出错误,让上层API处理(比如引导登录) throw error; } } // 辅助函数:获取当前token(根据实际存储方式实现) function getCurrentToken() { return localStorage.getItem('accessToken') || ''; } function getCurrentRefreshToken() { return localStorage.getItem('refreshToken') || ''; } function saveTokens(accessToken, refreshToken) { localStorage.setItem('accessToken', accessToken); localStorage.setItem('refreshToken', refreshToken); }
核心逻辑说明:
- 全局Promise缓存:用
refreshTokenPromise跟踪当前是否有正在进行的刷新请求,避免重复调用接口。 - 请求复用:当已有刷新请求时,后续调用直接等待同一个Promise,共用刷新后的token结果。
- 自动清理:刷新请求完成(无论成功或失败)后,自动清空全局Promise,确保下次需要时能发起新请求。
- 异常传递:刷新失败时抛出错误,让上层API函数可以处理异常场景(比如跳转到登录页)。
内容的提问来源于stack exchange,提问作者Rishi
相关产品推荐
相关产品推荐

