在 javascript 中实现请求数据预令牌策略,核心做法是利用 fetch 的拦截机制或封装一个高阶函数,在发起实际网络请求前,先通过同步或异步逻辑获取并注入认证令牌。具体而言,可以创建一个包装函数,该函数在调用 fetch 之前检查本地存储或内存中的令牌状态,若令牌过期则先刷新令牌,再将新的令牌附加到请求头 authorization 中。这样做能确保每次请求都携带有效的凭证,同时避免在业务代码中重复编写鉴权逻辑。

如何封装 fetch 以自动注入 Token
要实现自动注入,需要定义一个自定义的 requestWithToken 函数,它接收 URL 和可选的配置对象作为参数。在函数内部,首先从 localStorage 或全局状态中读取当前保存的 access_token。如果存在有效令牌,将其添加到 headers 的 Authorization 字段中,格式通常为 Bearer <token></token>。随后,调用原生的 fetch 方法发送请求。这种封装方式将鉴权细节与业务逻辑解耦,开发者只需调用封装后的函数即可。

function requestWithToken(url, options = {}) {
const token = localStorage.getItem('access_token');
if (token) {
options.headers = {
...options.headers,
'Authorization': `Bearer ${token}`
};
}
return fetch(url, options);
}
上述代码展示了基础实现。当调用 requestWithToken('/api/data') 时,函数会自动读取令牌并注入请求头。如果 localStorage 中没有令牌,请求将正常发出但不带认证信息,这适用于公开接口。开发者可以根据实际需求调整读取令牌的来源,例如使用 Vuex 或 Redux 等状态管理库中的全局变量。
立即学习“Java免费学习笔记(深入)”;
如何处理 Token 过期与刷新流程
在实际应用中,令牌通常具有较短的有效期,因此需要在请求失败后触发刷新机制。当服务器返回 401 Unauthorized 状态码时,表明当前令牌已失效。此时应拦截响应,暂停其他待处理的请求,并调用刷新接口获取新令牌。刷新成功后,更新本地存储的令牌,并重试原始请求。这一过程需要处理并发请求,防止多个请求同时触发刷新导致混乱。

async function refreshToken() {
const refresh_token = localStorage.getItem('refresh_token');
const response = await fetch('/auth/refresh', {
method: 'POST',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify({ token: refresh_token })
});
const data = await response.json();
localStorage.setItem('access_token', data.access_token);
return data.access_token;
}
async function requestWithRefresh(url, options = {}) {
try {
return await requestWithToken(url, options);
} catch (error) {
if (error.status === 401) {
const newToken = await refreshToken();
options.headers['Authorization'] = `Bearer ${newToken}`;
return requestWithToken(url, options);
}
throw error;
}
}
这段代码实现了基本的重试逻辑。当 requestWithToken 抛出错误且状态码为 401 时,程序会调用 refreshToken 获取新令牌,然后再次尝试请求。需要注意的是,简单的重试可能无法处理高并发场景下的竞态条件,生产环境建议使用队列机制来管理刷新请求。此外,刷新接口本身也需要安全保护,通常使用独立的 refresh_token 进行验证。
基于并发请求下 Token 刷新的具体行为
当多个请求同时遇到 401 错误时,如果每个请求都独立调用刷新接口,会导致多次不必要的网络请求,甚至可能因为刷新接口的限流而失败。为解决此问题,需要引入一个标志位或 Promise 队列来确保同一时间只有一个刷新操作在执行。其他请求应等待刷新完成后再重试。这种机制保证了系统的稳定性和效率,避免了资源浪费。
let isRefreshing = false;
let requestsQueue = [];
function flushRequestsQueue(newToken) {
requestsQueue.forEach(cb => cb(newToken));
requestsQueue = [];
}
async function requestWithQueue(url, options = {}) {
const attemptRequest = async () => {
try {
return await requestWithToken(url, options);
} catch (error) {
if (error.status === 401 && !isRefreshing) {
isRefreshing = true;
try {
await refreshToken();
isRefreshing = false;
flushRequestsQueue(localStorage.getItem('access_token'));
return requestWithToken(url, options);
} catch (refreshError) {
isRefreshing = false;
throw refreshError;
}
} else if (error.status === 401) {
return new Promise((resolve) => {
requestsQueue.push((newToken) => {
options.headers['Authorization'] = `Bearer ${newToken}`;
resolve(requestWithToken(url, options));
});
});
}
throw error;
}
};
return attemptRequest();
}
该实现引入了 isRefreshing 标志和 requestsQueue 数组。当第一个请求发现令牌过期时,设置标志并开始刷新,后续请求则将回调函数加入队列。刷新完成后,遍历队列执行回调,注入新令牌并重试。这种方式确保了在高并发场景下,只有唯一的刷新请求被发送,且所有受影响的请求都能正确获得新令牌后继续执行。开发者在使用时应注意清理队列,防止内存泄漏。


















