From 136cbb1e3bc056c88cfa21fca612d3ab72b4d119 Mon Sep 17 00:00:00 2001 From: Captain <49203535+MssText@users.noreply.github.com> Date: 星期六, 19 三月 2022 00:07:34 +0800 Subject: [PATCH] feat: add request retry (#1553) --- src/utils/http/axios/Axios.ts | 147 ++++++++++++++++++++++++++++++++++++------------ 1 files changed, 110 insertions(+), 37 deletions(-) diff --git a/src/utils/http/axios/Axios.ts b/src/utils/http/axios/Axios.ts index 1da69da..e3e912d 100644 --- a/src/utils/http/axios/Axios.ts +++ b/src/utils/http/axios/Axios.ts @@ -1,21 +1,22 @@ -import type { AxiosRequestConfig, AxiosInstance, AxiosResponse } from 'axios'; - +import type { AxiosRequestConfig, AxiosInstance, AxiosResponse, AxiosError } from 'axios'; +import type { RequestOptions, Result, UploadFileParams } from '/#/axios'; +import type { CreateAxiosOptions } from './axiosTransform'; import axios from 'axios'; +import qs from 'qs'; import { AxiosCanceler } from './axiosCancel'; import { isFunction } from '/@/utils/is'; import { cloneDeep } from 'lodash-es'; - -import { RequestOptions, CreateAxiosOptions, Result } from './types'; import { ContentTypeEnum } from '/@/enums/httpEnum'; +import { RequestEnum } from '/@/enums/httpEnum'; export * from './axiosTransform'; /** - * @description: axios妯″潡 + * @description: axios module */ export class VAxios { private axiosInstance: AxiosInstance; - private options: CreateAxiosOptions; + private readonly options: CreateAxiosOptions; constructor(options: CreateAxiosOptions) { this.options = options; @@ -24,7 +25,7 @@ } /** - * @description: 鍒涘缓axios瀹炰緥 + * @description: Create axios instance */ private createAxios(config: CreateAxiosOptions): void { this.axiosInstance = axios.create(config); @@ -40,7 +41,7 @@ } /** - * @description: 閲嶆柊閰嶇疆axios + * @description: Reconfigure axios */ configAxios(config: CreateAxiosOptions) { if (!this.axiosInstance) { @@ -50,7 +51,7 @@ } /** - * @description: 璁剧疆閫氱敤header + * @description: Set general header */ setHeader(headers: any): void { if (!this.axiosInstance) { @@ -60,7 +61,7 @@ } /** - * @description: 鎷︽埅鍣ㄩ厤缃� + * @description: Interceptor configuration */ private setupInterceptors() { const transform = this.getTransform(); @@ -76,22 +77,29 @@ const axiosCanceler = new AxiosCanceler(); - // 璇锋眰鎷︽埅鍣ㄩ厤缃鐞� + // Request interceptor configuration processing this.axiosInstance.interceptors.request.use((config: AxiosRequestConfig) => { - const { headers: { ignoreCancelToken } = { ignoreCancelToken: false } } = config; - !ignoreCancelToken && axiosCanceler.addPending(config); + // If cancel repeat request is turned on, then cancel repeat request is prohibited + // @ts-ignore + const { ignoreCancelToken } = config.requestOptions; + const ignoreCancel = + ignoreCancelToken !== undefined + ? ignoreCancelToken + : this.options.requestOptions?.ignoreCancelToken; + + !ignoreCancel && axiosCanceler.addPending(config); if (requestInterceptors && isFunction(requestInterceptors)) { - config = requestInterceptors(config); + config = requestInterceptors(config, this.options); } return config; }, undefined); - // 璇锋眰鎷︽埅鍣ㄩ敊璇崟鑾� + // Request interceptor error capture requestInterceptorsCatch && isFunction(requestInterceptorsCatch) && this.axiosInstance.interceptors.request.use(undefined, requestInterceptorsCatch); - // 鍝嶅簲缁撴灉鎷︽埅鍣ㄥ鐞� + // Response result interceptor processing this.axiosInstance.interceptors.response.use((res: AxiosResponse<any>) => { res && axiosCanceler.removePending(res.config); if (responseInterceptors && isFunction(responseInterceptors)) { @@ -100,63 +108,128 @@ return res; }, undefined); - // 鍝嶅簲缁撴灉鎷︽埅鍣ㄩ敊璇崟鑾� + // 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); + }); } /** - * @description: 鏂囦欢涓婁紶 + * @description: File Upload */ - uploadFiles(config: AxiosRequestConfig, params: File[]) { - const formData = new FormData(); + uploadFile<T = any>(config: AxiosRequestConfig, params: UploadFileParams) { + const formData = new window.FormData(); + const customFilename = params.name || 'file'; - Object.keys(params).forEach((key) => { - formData.append(key, params[key as any]); - }); + if (params.filename) { + formData.append(customFilename, params.file, params.filename); + } else { + formData.append(customFilename, params.file); + } - return this.request({ + if (params.data) { + Object.keys(params.data).forEach((key) => { + const value = params.data![key]; + if (Array.isArray(value)) { + value.forEach((item) => { + formData.append(`${key}[]`, item); + }); + return; + } + + formData.append(key, params.data![key]); + }); + } + + return this.axiosInstance.request<T>({ ...config, method: 'POST', data: formData, headers: { 'Content-type': ContentTypeEnum.FORM_DATA, + // @ts-ignore + ignoreCancelToken: true, }, }); } - /** - * @description: 璇锋眰鏂规硶 - */ + // support form-data + supportFormData(config: AxiosRequestConfig) { + const headers = config.headers || this.options.headers; + const contentType = headers?.['Content-Type'] || headers?.['content-type']; + + if ( + contentType !== ContentTypeEnum.FORM_URLENCODED || + !Reflect.has(config, 'data') || + config.method?.toUpperCase() === RequestEnum.GET + ) { + return config; + } + + return { + ...config, + data: qs.stringify(config.data, { arrayFormat: 'brackets' }), + }; + } + + get<T = any>(config: AxiosRequestConfig, options?: RequestOptions): Promise<T> { + return this.request({ ...config, method: 'GET' }, options); + } + + post<T = any>(config: AxiosRequestConfig, options?: RequestOptions): Promise<T> { + return this.request({ ...config, method: 'POST' }, options); + } + + put<T = any>(config: AxiosRequestConfig, options?: RequestOptions): Promise<T> { + return this.request({ ...config, method: 'PUT' }, options); + } + + delete<T = any>(config: AxiosRequestConfig, options?: RequestOptions): Promise<T> { + return this.request({ ...config, method: 'DELETE' }, options); + } + request<T = any>(config: AxiosRequestConfig, options?: RequestOptions): Promise<T> { - let conf: AxiosRequestConfig = cloneDeep(config); + let conf: CreateAxiosOptions = cloneDeep(config); const transform = this.getTransform(); const { requestOptions } = this.options; const opt: RequestOptions = Object.assign({}, requestOptions, options); - const { beforeRequestHook, requestCatch, transformRequestData } = transform || {}; + const { beforeRequestHook, requestCatchHook, transformRequestHook } = transform || {}; if (beforeRequestHook && isFunction(beforeRequestHook)) { conf = beforeRequestHook(conf, opt); } + conf.requestOptions = opt; + + conf = this.supportFormData(conf); + return new Promise((resolve, reject) => { this.axiosInstance .request<any, AxiosResponse<Result>>(conf) .then((res: AxiosResponse<Result>) => { - if (transformRequestData && isFunction(transformRequestData)) { - const ret = transformRequestData(res, opt); - ret !== undefined ? resolve(ret) : reject(new Error('request error!')); + if (transformRequestHook && isFunction(transformRequestHook)) { + try { + const ret = transformRequestHook(res, opt); + resolve(ret); + } catch (err) { + reject(err || new Error('request error!')); + } return; } - resolve((res as unknown) as Promise<T>); + resolve(res as unknown as Promise<T>); }) - .catch((e: Error) => { - if (requestCatch && isFunction(requestCatch)) { - reject(requestCatch(e)); + .catch((e: Error | AxiosError) => { + if (requestCatchHook && isFunction(requestCatchHook)) { + reject(requestCatchHook(e, opt)); return; } + if (axios.isAxiosError(e)) { + // rewrite error message from axios in here + } reject(e); }); }); -- Gitblit v1.8.0