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

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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.07.24 22:42:29