js中fetch无原生拦截器,需封装request函数模拟:自动注入token、捕获401后用refreshtoken刷新并重发请求,并发时通过promise缓存避免重复刷新。

在 JS 中,fetch 本身没有原生拦截器机制,但可以通过封装一个自定义的请求函数来模拟“拦截器”行为,在其中统一处理 Token 过期与自动刷新逻辑。核心思路是:捕获 401 响应 → 尝试用 refreshToken 刷新 accessToken → 成功后重发原请求;失败则跳转登录页。
封装 fetch 请求函数,统一注入 token 和错误处理
不直接调用 fetch,而是写一个 request 函数,所有业务请求都走它:
- 每次请求自动读取当前
accessToken放入Authorization头 - 对响应做统一检查:若返回 401,触发刷新流程;否则正常解析 JSON 或返回 Response
- 使用
Promise链和async/await控制流程,避免嵌套地狱
实现 refreshToken 并缓存新 accessToken
刷新 Token 是一个独立的 API 调用,通常需要当前的 refreshToken(存在 localStorage 或内存中):
- 调用
/auth/refresh接口,传入refreshToken - 成功后更新本地存储的
accessToken(和可能的新refreshToken) - 注意并发请求:多个 401 同时到来时,只允许一次刷新,其余等待同一结果(用 Promise 缓存解决)
重发原始请求(带新 token)并返回结果
刷新成功后,需用新 token 重新发起**原始请求**(包括 method、body、headers 等):
- 把原始
fetch参数(url、options)保存下来,供重试时复用 - 重试前清除旧 token 头,注入新 token
- 重试失败不再自动刷新,直接 reject,由上层处理(如登出)
示例代码(精简可运行版)
(假设 token 存于 localStorage,刷新接口为 /api/auth/refresh)
let refreshPromise = null;
async function refreshToken() {
if (refreshPromise) return refreshPromise;
refreshPromise = (async () => {
const rt = localStorage.getItem('refreshToken');
const res = await fetch('/api/auth/refresh', {
method: 'POST',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify({ refreshToken: rt })
});
if (!res.ok) throw new Error('Refresh failed');
const { accessToken, refreshToken: newRT } = await res.json();
localStorage.setItem('accessToken', accessToken);
if (newRT) localStorage.setItem('refreshToken', newRT);
return accessToken;
})();
// 清除缓存,避免下次仍用旧 promise
refreshPromise.then(() => refreshPromise = null, () => refreshPromise = null);
return refreshPromise;
}
export async function request(url, options = {}) {
let accessToken = localStorage.getItem('accessToken');
const headers = {
'Content-Type': 'application/json',
...options.headers,
...(accessToken ? { Authorization: `Bearer ${accessToken}` } : {})
};
const config = { ...options, headers };
try {
const res = await fetch(url, config);
if (res.status === 401) {
try {
accessToken = await refreshToken();
// 重试:更新 header 后再发一次
const retryConfig = {
...config,
headers: { ...config.headers, Authorization: `Bearer ${accessToken}` }
};
return await fetch(url, retryConfig).then(r => r.json());
} catch (e) {
// 刷新失败,清空 token 并跳登录
localStorage.removeItem('accessToken');
localStorage.removeItem('refreshToken');
window.location.href = '/login';
throw e;
}
}
if (!res.ok) throw new Error(`HTTP error! status: ${res.status}`);
return await res.json();
} catch (err) {
console.error('Request failed:', err);
throw err;
}
}











