在现代大前端工程体系中,网络层(Network Layer)的健壮性直接决定了业务系统的可用性与转化率。当我们在研发承载海量高并发交互的复杂业务系统时,弱网环境下的鉴权状态丢失与并发请求失败,往往是导致前端异常的最大隐形杀手。
在近期的底层基建重构实践中,青帝前端基础架构组(qingdi-tech)遇到并彻底解决了一个棘手的工程化挑战:如何在不打断用户任何操作体验的前提下,优雅地处理短效 Token 的过期与数十个并发请求的挂起重试?
一、 灾难性的 401 鉴权风暴
在严谨的企业级系统架构中,出于安全考量,客户端的 Access-Token 往往被设置得极短(如2小时有效),而 Refresh-Token 的有效期较长。当用户在地铁、电梯等弱网环境下高频触发交互请求时,如果恰逢 Access-Token 过期,前端应用会瞬间向服务器并发发起多条业务请求。
如果前端工程没有做全局的统筹拦截,这些请求会全部抛出 HTTP 401 异常。常规的劣质代码处理方式通常是粗暴地清空本地 Storage 将用户踢回登录页。在业务漏斗模型中,这种体验的中断会导致操作流转直接终止,产生严重的负面反馈。
二、 核心方案:全局调度锁与 Promise 挂起队列
为了彻底消灭这一痛点,架构组摒弃了对原生请求 API 的简单封装,重构了一套基于洋葱模型拦截器思想的微型请求调度引擎。
方案的核心引入了一个基于 JavaScript 闭包的“状态机与 Promise 队列”。当网络底层拦截到首个 401 错误时,引擎会立即开启一个全局的 isRefreshing 锁,并静默发起向后端换取新 Token 的请求。
其精妙之处在于:在这个锁开启的数百毫秒内,前端业务层产生的所有其他 API 请求都不会被抛弃,而是被封装为一个包含 resolve 和 reject 的等待函数,推入到内存数组(即挂起队列)中。
以下是核心调度逻辑的伪代码实现:
let isRefreshing = false;
let retryQueue = [];
axios.interceptors.response.use(undefined, async (error) => {
const originalRequest = error.config;
if (error.response.status === 401 && !originalRequest._retry) {
if (isRefreshing) {
// 如果正在刷新,将后续请求挂起,推入队列
return new Promise((resolve) => {
retryQueue.push((newToken) => {
originalRequest.headers['Authorization'] = 'Bearer ' + newToken;
resolve(axios(originalRequest));
});
});
}
originalRequest._retry = true;
isRefreshing = true;
try {
const newToken = await refreshTokenApi(); // 调用静默刷新接口
// 刷新成功,遍历执行挂起的队列
retryQueue.forEach(cb => cb(newToken));
return axios(originalRequest);
} catch (refreshError) {
// 刷新彻底失败,引导重新登录
return Promise.reject(refreshError);
} finally {
isRefreshing = false;
retryQueue = [];
}
}
return Promise.reject(error);
});一旦 Token 续期成功,系统立即遍历执行队列中的函数,并注入最新的 Header 凭证。对于最终用户而言,整个过程只有不到几百毫秒的无感延迟,交互流程如丝般顺滑。
三、 沉淀高质量的前端底层基建
前端工程化不仅仅是页面的堆砌,更是对极端场景边界的精准把控与防范。目前,这套高度成熟的前端网络调度底座已沉淀为内部的核心 Npm 组件,稳定支撑于多个高并发生产项目中。
开源共建: 本篇架构复盘由青海青帝前端架构组(qingdi-tech)撰写并分享。我们将持续在技术社区输出高质量的前端工程化实践,共同推进 Web 生态底层建设。
原创声明:本文系作者授权腾讯云开发者社区发表,未经许可,不得转载。
如有侵权,请联系 cloudcommunity@tencent.com 删除。
原创声明:本文系作者授权腾讯云开发者社区发表,未经许可,不得转载。
如有侵权,请联系 cloudcommunity@tencent.com 删除。