前端添加防抖/节流机制的无感刷新Token实现(基于Axios)
为了防止在短时间内多次触发token刷新,我们可以添加防抖(debounce)或节流(throttle)机制。下面是优化后的代码:
- 工具函数(防抖和节流)
javascript
// 防抖函数
function debounce(fn, delay) {
let timer = null;
return function(...args) {
if (timer) clearTimeout(timer);
timer = setTimeout(() => {
fn.apply(this, args);
}, delay);
};
}
// 节流函数
function throttle(fn, delay) {
let lastTime = 0;
return function(...args) {
const now = Date.now();
if (now - lastTime >= delay) {
fn.apply(this, args);
lastTime = now;
}
};
}
- 优化后的刷新Token逻辑
javascript
let isRefreshing = false;
let requests = [];
// 添加节流时间(例如5秒内不重复刷新)
const REFRESH_THROTTLE_TIME = 5000;
let lastRefreshTime = 0;
// 带节流的刷新token函数
const refreshTokenWithThrottle = throttle(async () => {
try {
const refreshToken = localStorage.getItem('refresh_token');
if (!refreshToken) {
throw new Error('No refresh token available');
}
const res = await axios.post('/auth/refreshToken', { refreshToken });
const { access_token, refresh_token } = res.data;
localStorage.setItem('access_token', access_token);
localStorage.setItem('refresh_token', refresh_token);
return access_token;
} catch (error) {
// 清除token并跳转登录
localStorage.removeItem('access_token');
localStorage.removeItem('refresh_token');
window.location.href = '/login';
throw error;
}
}, REFRESH_THROTTLE_TIME);
// 带防抖的请求重试函数
const retryRequests = debounce(() => {
requests.forEach(cb => cb());
requests = [];
}, 100);
3. 优化后的响应拦截器
javascript
service.interceptors.response.use(
response => response.data,
async error => {
const { config, response } = error;
if (!response || response.status !== 401) {
return Promise.reject(error);
}
if (config.url.includes('/refreshToken')) {
window.location.href = '/login';
return Promise.reject(error);
}
// 检查是否在节流时间内
const now = Date.now();
if (isRefreshing && now - lastRefreshTime < REFRESH_THROTTLE_TIME) {
return new Promise(resolve => {
requests.push(() => resolve(service(config)));
});
}
isRefreshing = true;
lastRefreshTime = now;
try {
const newToken = await refreshTokenWithThrottle();
// 更新当前请求的header
if (config.headers) {
config.headers['Authorization'] = `Bearer ${newToken}`;
}
// 防抖执行重试
retryRequests();
return service(config);
} catch (refreshError) {
return Promise.reject(refreshError);
} finally {
isRefreshing = false;
}
}
);
- 完整优化点说明
节流刷新Token:设置了REFRESH_THROTTLE_TIME(如5秒)
在节流时间内不会重复刷新token,避免短时间内因多个接口401导致多次刷新
防抖重试请求:使用防抖技术合并短时间内多个重试请求,减少不必要的重复请求
时间戳控制:记录最后一次刷新时间lastRefreshTime,精确控制节流间隔
错误处理优化:统一处理刷新失败的情况,确保最终会跳转登录页
- 配置建议
javascript
// 可以根据实际需求调整这些参数
const CONFIG = {
refreshThrottleTime: 5000, // 5秒内不重复刷新
retryDebounceTime: 100, // 100ms内合并重试请求
maxRetryTimes: 3, // 最大重试次数(可选)
};
- 额外优化建议
添加最大重试次数限制:
javascript
let retryCount = 0;
const MAX_RETRY_TIMES = 3;
// 在刷新token前检查
if (retryCount >= MAX_RETRY_TIMES) {
localStorage.removeItem('access_token');
localStorage.removeItem('refresh_token');
window.location.href = '/login';
return Promise.reject(new Error('Max retry times exceeded'));
}
网络状态检测:
javascript
// 可以在刷新前检查网络状态
if (!navigator.onLine) {
return Promise.reject(new Error('Network unavailable'));
}
Token过期预判:
javascript
// 可以在请求前判断token是否即将过期
function isTokenExpiringSoon(token) {
// 解析token的exp字段
// 如果剩余时间小于某个阈值(如5分钟),返回true
}
这种实现方式既防止了频繁刷新token,又能保证用户体验的无感知,同时避免了不必要的性能开销。