feat: add request retry (#1553)
| | |
| | | return resultSuccess(undefined, { message: 'Token has been destroyed' }); |
| | | }, |
| | | }, |
| | | { |
| | | url: '/basic-api/testRetry', |
| | | statusCode: 405, |
| | | method: 'get', |
| | | response: () => { |
| | | return resultError('Error!'); |
| | | }, |
| | | }, |
| | | ] as MockMethod[]; |
| | |
| | | Logout = '/logout', |
| | | GetUserInfo = '/getUserInfo', |
| | | GetPermCode = '/getPermCode', |
| | | TestRetry = '/testRetry', |
| | | } |
| | | |
| | | /** |
| | |
| | | export function doLogout() { |
| | | return defHttp.get({ url: Api.Logout }); |
| | | } |
| | | |
| | | export function testRetry() { |
| | | return defHttp.get( |
| | | { url: Api.TestRetry }, |
| | | { |
| | | retryRequest: { |
| | | isOpenRetry: true, |
| | | count: 5, |
| | | waitTime: 1000, |
| | | }, |
| | | }, |
| | | ); |
| | | } |
| | |
| | | breadcrumb: 'Breadcrumbs', |
| | | breadcrumbFlat: 'Flat Mode', |
| | | breadcrumbFlatDetail: 'Flat mode details', |
| | | requestDemo: 'Retry request demo', |
| | | |
| | | breadcrumbChildren: 'Level mode', |
| | | breadcrumbChildrenDetail: 'Level mode detail', |
| | |
| | | ws: 'websocket测试', |
| | | breadcrumb: '面包屑导航', |
| | | breadcrumbFlat: '平级模式', |
| | | requestDemo: '测试请求重试', |
| | | breadcrumbFlatDetail: '平级详情', |
| | | breadcrumbChildren: '层级模式', |
| | | breadcrumbChildrenDetail: '层级详情', |
| | |
| | | }, |
| | | }, |
| | | { |
| | | path: 'request', |
| | | name: 'RequestDemo', |
| | | // @ts-ignore |
| | | component: () => import('/@/views/demo/feat/request-demo/index.vue'), |
| | | meta: { |
| | | title: t('routes.demo.feat.requestDemo'), |
| | | }, |
| | | }, |
| | | { |
| | | path: 'session-timeout', |
| | | name: 'SessionTimeout', |
| | | component: () => import('/@/views/demo/feat/session-timeout/index.vue'), |
| | |
| | | // Response result interceptor error capture |
| | | responseInterceptorsCatch && |
| | | isFunction(responseInterceptorsCatch) && |
| | | this.axiosInstance.interceptors.response.use(undefined, responseInterceptorsCatch); |
| | | this.axiosInstance.interceptors.response.use(undefined, (error) => { |
| | | // @ts-ignore |
| | | responseInterceptorsCatch(this.axiosInstance, error); |
| | | }); |
| | | } |
| | | |
| | | /** |
New file |
| | |
| | | import { AxiosError, AxiosInstance } from 'axios'; |
| | | /** |
| | | * 请求重试机制 |
| | | */ |
| | | |
| | | export class AxiosRetry { |
| | | /** |
| | | * 重试 |
| | | */ |
| | | retry(AxiosInstance: AxiosInstance, error: AxiosError) { |
| | | // @ts-ignore |
| | | const { config } = error.response; |
| | | const { waitTime, count } = config?.requestOptions?.retryRequest; |
| | | config.__retryCount = config.__retryCount || 0; |
| | | if (config.__retryCount >= count) { |
| | | return Promise.reject(error); |
| | | } |
| | | config.__retryCount += 1; |
| | | return this.delay(waitTime).then(() => AxiosInstance(config)); |
| | | } |
| | | |
| | | /** |
| | | * 延迟 |
| | | */ |
| | | private delay(waitTime: number) { |
| | | return new Promise((resolve) => setTimeout(resolve, waitTime)); |
| | | } |
| | | } |
| | |
| | | /** |
| | | * @description: 请求之后的拦截器错误处理 |
| | | */ |
| | | responseInterceptorsCatch?: (error: Error) => void; |
| | | responseInterceptorsCatch?: (axiosInstance: AxiosResponse, error: Error) => void; |
| | | } |
| | |
| | | import { useI18n } from '/@/hooks/web/useI18n'; |
| | | import { joinTimestamp, formatRequestDate } from './helper'; |
| | | import { useUserStoreWithOut } from '/@/store/modules/user'; |
| | | import { AxiosRetry } from '/@/utils/http/axios/axiosRetry'; |
| | | |
| | | const globSetting = useGlobSetting(); |
| | | const urlPrefix = globSetting.urlPrefix; |
| | |
| | | /** |
| | | * @description: 响应错误处理 |
| | | */ |
| | | responseInterceptorsCatch: (error: any) => { |
| | | responseInterceptorsCatch: (axiosInstance: AxiosResponse, error: any) => { |
| | | const { t } = useI18n(); |
| | | const errorLogStore = useErrorLogStoreWithOut(); |
| | | errorLogStore.addAjaxErrorInfo(error); |
| | |
| | | } |
| | | |
| | | checkStatus(error?.response?.status, msg, errorMessageMode); |
| | | |
| | | // 添加自动重试机制 保险起见 只针对GET请求 |
| | | const retryRequest = new AxiosRetry(); |
| | | const { isOpenRetry } = config.requestOptions.retryRequest; |
| | | config.method?.toUpperCase() === RequestEnum.GET && |
| | | isOpenRetry && |
| | | // @ts-ignore |
| | | retryRequest.retry(axiosInstance, error); |
| | | return Promise.reject(error); |
| | | }, |
| | | }; |
| | |
| | | ignoreCancelToken: true, |
| | | // 是否携带token |
| | | withToken: true, |
| | | retryRequest: { |
| | | isOpenRetry: true, |
| | | count: 5, |
| | | waitTime: 100, |
| | | }, |
| | | }, |
| | | }, |
| | | opt || {}, |
New file |
| | |
| | | <template> |
| | | <div class="request-box"> |
| | | <a-button @click="handleClick" color="primary"> 点击会重新发起请求5次 </a-button> |
| | | <p>打开浏览器的network面板,可以看到发出了六次请求</p> |
| | | </div> |
| | | </template> |
| | | <script lang="ts" setup> |
| | | import { testRetry } from '/@/api/sys/user'; |
| | | // @ts-ignore |
| | | const handleClick = async () => { |
| | | await testRetry(); |
| | | }; |
| | | </script> |
| | | |
| | | <style lang="less"> |
| | | .request-box { |
| | | margin: 50px; |
| | | } |
| | | |
| | | p { |
| | | margin-top: 10px; |
| | | } |
| | | </style> |
| | |
| | | ignoreCancelToken?: boolean; |
| | | // Whether to send token in header |
| | | withToken?: boolean; |
| | | // 请求重试机制 |
| | | retryRequest?: RetryRequest; |
| | | } |
| | | |
| | | export interface RetryRequest { |
| | | isOpenRetry: boolean; |
| | | count: number; |
| | | waitTime: number; |
| | | } |
| | | export interface Result<T = any> { |
| | | code: number; |
| | | type: 'success' | 'error' | 'warning'; |