diff --git a/packages/axios/src/index.ts b/packages/axios/src/index.ts index c29d9d2b..b9866b6c 100644 --- a/packages/axios/src/index.ts +++ b/packages/axios/src/index.ts @@ -13,11 +13,12 @@ import type { ResponseType } from './type'; -function createCommonRequest( - axiosConfig?: CreateAxiosDefaults, - options?: Partial> -) { - const opts = createDefaultOptions(options); +function createCommonRequest< + ResponseData, + ApiData = ResponseData, + State extends Record = Record +>(axiosConfig?: CreateAxiosDefaults, options?: Partial>) { + const opts = createDefaultOptions(options); const axiosConf = createAxiosConfig(axiosConfig); const instance = axios.create(axiosConf); @@ -109,15 +110,19 @@ function createCommonRequest( * @param axiosConfig axios config * @param options request options */ -export function createRequest>( +export function createRequest>( axiosConfig?: CreateAxiosDefaults, - options?: Partial> + options?: Partial> ) { - const { instance, opts, cancelRequest, cancelAllRequest } = createCommonRequest(axiosConfig, options); + const { instance, opts, cancelRequest, cancelAllRequest } = createCommonRequest( + axiosConfig, + options + ); - const request: RequestInstance = async function request( - config: CustomAxiosRequestConfig - ) { + const request: RequestInstance = async function request< + T extends ApiData = ApiData, + R extends ResponseType = 'json' + >(config: CustomAxiosRequestConfig) { const response: AxiosResponse = await instance(config); const responseType = response.config?.responseType || 'json'; @@ -127,7 +132,7 @@ export function createRequest; - } as RequestInstance; + } as RequestInstance; request.cancelRequest = cancelRequest; request.cancelAllRequest = cancelAllRequest; @@ -144,14 +149,17 @@ export function createRequest>( +export function createFlatRequest>( axiosConfig?: CreateAxiosDefaults, - options?: Partial> + options?: Partial> ) { - const { instance, opts, cancelRequest, cancelAllRequest } = createCommonRequest(axiosConfig, options); + const { instance, opts, cancelRequest, cancelAllRequest } = createCommonRequest( + axiosConfig, + options + ); - const flatRequest: FlatRequestInstance = async function flatRequest< - T = any, + const flatRequest: FlatRequestInstance = async function flatRequest< + T extends ApiData = ApiData, R extends ResponseType = 'json' >(config: CustomAxiosRequestConfig) { try { @@ -160,20 +168,22 @@ export function createFlatRequest, error: null }; + return { data: response.data as MappedType, error: null, response }; } catch (error) { return { data: null, error, response: (error as AxiosError).response }; } - } as FlatRequestInstance; + } as FlatRequestInstance; flatRequest.cancelRequest = cancelRequest; flatRequest.cancelAllRequest = cancelAllRequest; - flatRequest.state = {} as State; + flatRequest.state = { + ...opts.defaultState + } as State; return flatRequest; } diff --git a/packages/axios/src/options.ts b/packages/axios/src/options.ts index 8b2b116a..ff0c4714 100644 --- a/packages/axios/src/options.ts +++ b/packages/axios/src/options.ts @@ -4,12 +4,16 @@ import { stringify } from 'qs'; import { isHttpSuccess } from './shared'; import type { RequestOption } from './type'; -export function createDefaultOptions(options?: Partial>) { - const opts: RequestOption = { +export function createDefaultOptions< + ResponseData, + ApiData = ResponseData, + State extends Record = Record +>(options?: Partial>) { + const opts: RequestOption = { onRequest: async config => config, isBackendSuccess: _response => true, onBackendFail: async () => {}, - transformBackendResponse: async response => response.data, + transformBackendResponse: async response => response.data as unknown as ApiData, onError: async () => {} }; diff --git a/packages/axios/src/type.ts b/packages/axios/src/type.ts index 644847ff..bc0bcd9f 100644 --- a/packages/axios/src/type.ts +++ b/packages/axios/src/type.ts @@ -8,7 +8,23 @@ export type ContentType = | 'application/x-www-form-urlencoded' | 'application/octet-stream'; -export interface RequestOption { +export type ResponseTransform = (input: Input) => Output | Promise; + +export interface RequestOption< + ResponseData, + ApiData = ResponseData, + State extends Record = Record +> { + /** + * The default state + */ + defaultState?: State; + /** + * transform the response data to the api data + * + * @param response Axios response + */ + transformBackendResponse: ResponseTransform, ApiData>; /** * The hook before request * @@ -35,12 +51,6 @@ export interface RequestOption { response: AxiosResponse, instance: AxiosInstance ) => Promise | Promise; - /** - * transform backend response when the responseType is json - * - * @param response Axios response - */ - transformBackendResponse(response: AxiosResponse): any | Promise; /** * The hook to handle error * @@ -68,7 +78,7 @@ export type CustomAxiosRequestConfig = Omit { +export interface RequestInstanceCommon> { /** * cancel the request by request id * @@ -84,32 +94,35 @@ export interface RequestInstanceCommon { */ cancelAllRequest: () => void; /** you can set custom state in the request instance */ - state: T; + state: State; } /** The request instance */ -export interface RequestInstance> extends RequestInstanceCommon { - (config: CustomAxiosRequestConfig): Promise>; +export interface RequestInstance> extends RequestInstanceCommon { + ( + config: CustomAxiosRequestConfig + ): Promise>; } -export type FlatResponseSuccessData = { - data: T; +export type FlatResponseSuccessData = { + data: ApiData; error: null; response: AxiosResponse; }; -export type FlatResponseFailData = { +export type FlatResponseFailData = { data: null; error: AxiosError; response: AxiosResponse; }; -export type FlatResponseData = - | FlatResponseSuccessData +export type FlatResponseData = + | FlatResponseSuccessData | FlatResponseFailData; -export interface FlatRequestInstance, ResponseData = any> extends RequestInstanceCommon { - ( +export interface FlatRequestInstance> + extends RequestInstanceCommon { + ( config: CustomAxiosRequestConfig - ): Promise, ResponseData>>; + ): Promise>>; } diff --git a/packages/hooks/src/use-request.ts b/packages/hooks/src/use-request.ts index a0a40e63..50d0e9e4 100644 --- a/packages/hooks/src/use-request.ts +++ b/packages/hooks/src/use-request.ts @@ -6,31 +6,31 @@ import type { CreateAxiosDefaults, CustomAxiosRequestConfig, MappedType, + RequestInstanceCommon, RequestOption, ResponseType } from '@sa/axios'; import useLoading from './use-loading'; -export type HookRequestInstanceResponseSuccessData = { - data: Ref; +export type HookRequestInstanceResponseSuccessData = { + data: Ref; error: Ref; }; -export type HookRequestInstanceResponseFailData = { +export type HookRequestInstanceResponseFailData = { data: Ref; error: Ref>; }; -export type HookRequestInstanceResponseData = { +export type HookRequestInstanceResponseData = { loading: Ref; -} & (HookRequestInstanceResponseSuccessData | HookRequestInstanceResponseFailData); +} & (HookRequestInstanceResponseSuccessData | HookRequestInstanceResponseFailData); -export interface HookRequestInstance { - ( +export interface HookRequestInstance> + extends RequestInstanceCommon { + ( config: CustomAxiosRequestConfig - ): HookRequestInstanceResponseData, ResponseData>; - cancelRequest: (requestId: string) => void; - cancelAllRequest: () => void; + ): HookRequestInstanceResponseData>; } /** @@ -39,25 +39,26 @@ export interface HookRequestInstance { * @param axiosConfig * @param options */ -export default function createHookRequest( +export default function createHookRequest>( axiosConfig?: CreateAxiosDefaults, - options?: Partial> + options?: Partial> ) { - const request = createFlatRequest(axiosConfig, options); + const request = createFlatRequest(axiosConfig, options); - const hookRequest: HookRequestInstance = function hookRequest( - config: CustomAxiosRequestConfig - ) { + const hookRequest: HookRequestInstance = function hookRequest< + T extends ApiData = ApiData, + R extends ResponseType = 'json' + >(config: CustomAxiosRequestConfig) { const { loading, startLoading, endLoading } = useLoading(); - const data = ref | null>(null) as Ref>; - const error = ref | null>(null) as Ref | null>; + const data = ref(null) as Ref>; + const error = ref(null) as Ref | null>; startLoading(); request(config).then(res => { if (res.data) { - data.value = res.data; + data.value = res.data as MappedType; } else { error.value = res.error; } @@ -70,7 +71,7 @@ export default function createHookRequest( data, error }; - } as HookRequestInstance; + } as HookRequestInstance; hookRequest.cancelRequest = request.cancelRequest; hookRequest.cancelAllRequest = request.cancelAllRequest; diff --git a/src/service/request/index.ts b/src/service/request/index.ts index a99657a2..84f3cd5c 100644 --- a/src/service/request/index.ts +++ b/src/service/request/index.ts @@ -10,7 +10,7 @@ import type { RequestInstanceState } from './type'; const isHttpProxy = import.meta.env.DEV && import.meta.env.VITE_HTTP_PROXY === 'Y'; const { baseURL, otherBaseURL } = getServiceBaseURL(import.meta.env, isHttpProxy); -export const request = createFlatRequest( +export const request = createFlatRequest( { baseURL, headers: { @@ -18,6 +18,13 @@ export const request = createFlatRequest>) { + return response.data.data; + }, async onRequest(config) { const Authorization = getAuthorization(); Object.assign(config.headers, { Authorization }); @@ -91,9 +98,6 @@ export const request = createFlatRequest( +export const demoRequest = createRequest( { baseURL: otherBaseURL.demo }, { + transformBackendResponse(response: AxiosResponse) { + return response.data.result; + }, async onRequest(config) { const { headers } = config; @@ -147,9 +154,6 @@ export const demoRequest = createRequest( // when the backend response code is not "200", it means the request is fail // for example: the token is expired, refresh token and retry request }, - transformBackendResponse(response) { - return response.data.result; - }, onError(error) { // when the request is fail, you can show error message diff --git a/src/service/request/shared.ts b/src/service/request/shared.ts index 162d4706..9d773b69 100644 --- a/src/service/request/shared.ts +++ b/src/service/request/shared.ts @@ -28,14 +28,14 @@ async function handleRefreshToken() { } export async function handleExpiredRequest(state: RequestInstanceState) { - if (!state.refreshTokenFn) { - state.refreshTokenFn = handleRefreshToken(); + if (!state.refreshTokenPromise) { + state.refreshTokenPromise = handleRefreshToken(); } - const success = await state.refreshTokenFn; + const success = await state.refreshTokenPromise; setTimeout(() => { - state.refreshTokenFn = null; + state.refreshTokenPromise = null; }, 1000); return success; diff --git a/src/service/request/type.ts b/src/service/request/type.ts index 4e4a2f71..f72b58a8 100644 --- a/src/service/request/type.ts +++ b/src/service/request/type.ts @@ -1,6 +1,7 @@ export interface RequestInstanceState { - /** whether the request is refreshing token */ - refreshTokenFn: Promise | null; + /** the promise of refreshing token */ + refreshTokenPromise: Promise | null; /** the request error message stack */ errMsgStack: string[]; + [key: string]: unknown; }