环境
Vue3、soybean admin: “1.0.0”(native-ui: “2.38.0”)、pnpm: “8.5.3”、axios: “1.6.7”、axios-retry: “4.0.0”
问题
axios执行了拦截器 instance.interceptors.request.use、instance.interceptors.response.use方法,再做token过期(状态码为50020)需重新跳转登录页面的逻辑时,发现onBackendFail方法执行了两次,导致登录页跳转了两次,提示了两次
解决方法
1. 前提
先排查是不是重复执行了instance.interceptors方法,或者instance实例化了多次,调用了多次
2. 分析
粗略看代码发现,是因为soybean admin使用 axios-retry,让失败的请求重新发起请求,我们可以去掉这个库,不使用该功能(一般应该是网络问题会用到),但是我这边是选择更改配置:
调用axiosRetry的地方:
// 文件路径:packages\axios\src\index.ts
function createCommonRequest<ResponseData = any>(
axiosConfig?: CreateAxiosDefaults,
options?: Partial<RequestOption<ResponseData>>
) {
const opts = createDefaultOptions<ResponseData>(options);
const axiosConf = createAxiosConfig(axiosConfig);
const instance = axios.create(axiosConf);
const cancelTokenSourceMap = new Map<string, CancelTokenSource>();
// config axios retry
const retryOptions = createRetryOptions(axiosConf);
axiosRetry(instance, retryOptions); // 这里调用了axiosRetry,请求失败时会自动再次发起请求
instance.interceptors.request.use(conf => {
const config: InternalAxiosRequestConfig = { ...conf };
// set request id
const requestId = nanoid();
config.headers.set(REQUEST_ID_KEY, requestId);
// config cancel token
const cancelTokenSource = axios.CancelToken.source();
config.cancelToken = cancelTokenSource.token;
cancelTokenSourceMap.set(requestId, cancelTokenSource);
// handle config by hook
const handledConfig = opts.onRequest?.(config) || config;
return handledConfig;
});
instance.interceptors.response.use(
async response => {
if (opts.isBackendSuccess(response)) {
return Promise.resolve(response);
}
const fail = await opts.onBackendFail(response, instance);
if (fail) {
return fail;
}
const backendError = new AxiosError<ResponseData>(
'the backend request error',
BACKEND_ERROR_CODE,
response.config,
response.request,
response
);
await opts.onError(backendError);
return Promise.reject(backendError);
},
async (error: AxiosError<ResponseData>) => {
await opts.onError(error);
return Promise.reject(error);
}
);
function cancelRequest(requestId: string) {
const cancelTokenSource = cancelTokenSourceMap.get(requestId);
if (cancelTokenSource) {
cancelTokenSource.cancel();
cancelTokenSourceMap.delete(requestId);
}
}
function cancelAllRequest() {
cancelTokenSourceMap.forEach(cancelTokenSource => {
cancelTokenSource.cancel();
});
cancelTokenSourceMap.clear();
}
return {
instance,
opts,
cancelRequest,
cancelAllRequest
};
}
3. 解决
更改一下Options配置,还参考了一些配置例子
博客:axios-retry、网友博客
原来的配置:
// 文件路径:packages\axios\src\options.ts
export function createRetryOptions(config?: Partial<CreateAxiosDefaults>) {
const retryConfig: IAxiosRetryConfig = {
retries: 3
};
Object.assign(retryConfig, config);
return retryConfig;
}
更改后的配置:
export function createRetryOptions(config?: Partial<CreateAxiosDefaults>) {
const retryConfig: IAxiosRetryConfig = {
retries: 1, // 重试次数
retryCondition: (error) => {
// 非token失效需要重试
return error?.response?.code !== TOKEN_EXPIRED_CODE || axiosRetry.isNetworkOrIdempotentRequestError(error);
},
};
Object.assign(retryConfig, config);
return retryConfig;
}