1.技术背景
在追求网络安全的大环境下,前端在处理网络请求的安全性控制有了更高的需求,回归问题的本质,处理token过期问题。
1.1 token过期需要解决的几个痛点
i. 怎么在响应式拦截器中重新发起请求
ii. 怎么处理token过期后大量请求并发
iii. token过期后在响应拦截器中怎么重新获取token
2.技术应用
在解决上面提到的三个痛点之前,我们需要着重了解两个知识点:
- axios拦截器
- promise的三种状态success(resolve) fail(reject) pending
这边我不着重讲述,相信大家已经能熟练掌握这两个知识点。
axios响应拦截器中,提取response.config这个属性中包含了所有请求相关的参数,所以我们可以使用axios(response.config)重新发起请求。
promise大家最常用的是成功态和失败态,但很多时候我们忽略了他的pending态,当Promise对象的resolve方法未被调用的情况下他始终处于pending (等待)状态。
3.技术实现
3.1响应式拦截器中获取token并重新发起请求
首先我们来解决第一个痛点——怎么在响应式拦截器中重新发起请求 ?
miniAxios.interceptors.response.use(
async response => {
const config = response.config as IConfig;
const resData = response.data;
if (resData?.code === REQUEST_SUCCESS_CODE) {
// 请求成功
return Promise.resolve(resData?.data);
}
// token过期
if (resData.subCode === 'user.token.expired') {
// 重新请求token
const authCode = await getAuthCodePromise();
const [err, res] = await authBase(authCode);
alipayUserId = res?.alipayUserId || '';
userToken = res?.userToken || '';
config.headers.userToken = userToken;
// 重置baseUrl 特别注意:如果不重置会出现/api/api
config.baseURL = '';
// 重新请求
return miniAxios(config);
}
// 错误处理
return Promise.reject(resData);
},
err => {
},
);
3.2 控制处理token过期后大量请求并发
步骤一:我们需要在请求拦截器第一个返回token过期的接口去处理
步骤二:拦截后面返回的所有请求,防止大量失效接口返回,同时通过promise等待队列存储后面的所有请求
步骤三:重新获取token,等新token返回,释放promise等待队列中所有的请求(即重新发起请求)
let userToken = '';
// token刷新标识位
let isRefreshing = false;
// 请求等待队列
const requestList = [];
miniAxios.interceptors.response.use(
async response => {
// 获取请求数据
const config = response.config as IConfig;
if (config.loading) {
closeLoading();
}
// 成功
const resData = response.data;
if (resData?.code === REQUEST_SUCCESS_CODE) {
// 请求成功
return Promise.resolve(resData?.data);
}
// token过期
if (resData.subCode === 'user.token.expired') {
if (!isRefreshing) {
// 不在刷新
// 刷新标识位置为true
isRefreshing = true;
// 请求token
const authCode = await getAuthCodePromise();
const [err, res] = await authBase(authCode);
alipayUserId = res?.alipayUserId || '';
userToken = res?.userToken || '';
config.headers.userToken = userToken;
// 重置baseUrl 特别注意:如果不重置会出现/api/api
config.baseURL = '';
// 拿到新token,resolve所有请求(释放所有promise)
requestList.forEach(cb => cb(userToken));
// 刷新结束
isRefreshing = false;
// 重新请求
return miniAxios(config);
}
// 将所有请求暂存与请求队列中,利用promise的pending状态
// 核心代码逻辑: 只要promise中的resolve没有调用,他会一直处于pending状态
return new Promise(resolve => {
// 加入请求队列
requestList.push(token => {
// 重置baseUrl 特别注意:如果不重置会出现/api/api
config.baseURL = '';
// 更新token
config.headers.userToken = token;
// 将promise.resolve塞入队列
resolve(miniAxios(config));
});
});
}
// 错误处理
return Promise.reject(resData);
},