refactor: 将请求封装重构为函数模式

This commit is contained in:
yangsy
2026-01-05 11:30:20 +08:00
parent fd70f63fc9
commit 263dd5edfc
4 changed files with 52 additions and 55 deletions

164
src/utils/http-client.ts Normal file
View File

@@ -0,0 +1,164 @@
import type { Result } from '@/types';
import axios, { isAxiosError, type AxiosError, type AxiosRequestConfig, type AxiosResponse, type CreateAxiosDefaults, type InternalAxiosRequestConfig } from 'axios';
export type HttpResponse<T> = [err: AxiosError | null, data: T | null, resp: Result<T> | null];
export interface HttpRequestOptions extends CreateAxiosDefaults {
requestInterceptor?: (config: InternalAxiosRequestConfig) => InternalAxiosRequestConfig | Promise<InternalAxiosRequestConfig>;
responseInterceptor?: (resp: AxiosResponse) => AxiosResponse | Promise<AxiosResponse>;
responseErrorInterceptor?: (error: any) => any;
}
export const createHttpClient = (config?: HttpRequestOptions) => {
const defaultRequestInterceptor = (config: InternalAxiosRequestConfig) => config;
const defaultResponseInterceptor = (response: AxiosResponse) => response;
const defaultResponseErrorInterceptor = (error: any) => {
if (isAxiosError(error)) {
if (error.status === 401) {
// 处理 401 错误
}
if (error.status === 404) {
// 处理 404 错误
}
}
return Promise.reject(error);
};
const requestInterceptor = config?.requestInterceptor ?? defaultRequestInterceptor;
const responseInterceptor = config?.responseInterceptor ?? defaultResponseInterceptor;
const responseErrorInterceptor = config?.responseErrorInterceptor ?? defaultResponseErrorInterceptor;
const instance = axios.create(config);
instance.interceptors.request.use(requestInterceptor);
instance.interceptors.response.use(responseInterceptor, responseErrorInterceptor);
const httpGet = <T>(url: string, options?: AxiosRequestConfig & { retRaw?: boolean }): Promise<HttpResponse<T>> => {
const { retRaw, ...reqConfig } = options ?? {};
return new Promise((resolve) => {
instance
.get(url, {
...reqConfig,
})
.then((res) => {
if (retRaw) {
resolve([null, res.data as T, null]);
} else {
const resData = res.data as Result<T>;
resolve([null, resData.data, resData]);
}
})
.catch((err) => {
resolve([err as AxiosError, null, null]);
});
});
};
const httpPost = <T>(url: string, data?: AxiosRequestConfig['data'], options?: Partial<Omit<AxiosRequestConfig, 'data'>> & { retRaw?: boolean; upload?: boolean }): Promise<HttpResponse<T>> => {
const { retRaw, upload, ...reqConfig } = options ?? {};
return new Promise((resolve) => {
instance
.post(url, data, { headers: { 'content-type': upload ? 'multipart/form-data' : 'application/json' }, ...reqConfig })
.then((res) => {
const resData = res.data;
if (retRaw) {
resolve([null, resData as T, null]);
} else {
resolve([null, resData.data as T, resData as Result<T>]);
}
})
.catch((err) => {
resolve([err as AxiosError, null, null]);
});
});
};
const httpPut = <T>(url: string, data?: AxiosRequestConfig['data'], options?: Partial<Omit<AxiosRequestConfig, 'data'>>): Promise<HttpResponse<T>> => {
const reqConfig = options ?? {};
return new Promise((resolve) => {
instance
.put<Result<T>>(url, data, { ...reqConfig })
.then((res) => {
resolve([null, res.data.data, res.data]);
})
.catch((err) => {
resolve([err as AxiosError, null, null]);
});
});
};
const httpDelete = <T>(url: string, idList: string[], options?: Partial<Omit<AxiosRequestConfig, 'data'>>): Promise<HttpResponse<T>> => {
const reqConfig = options ?? {};
return new Promise((resolve) => {
instance
.delete<Result<T>>(url, { ...reqConfig, data: idList })
.then((res) => {
resolve([null, res.data.data, res.data]);
})
.catch((err) => {
resolve([err as AxiosError, null, null]);
});
});
};
return {
get clientInstance() {
return instance;
},
get: httpGet,
post: httpPost,
put: httpPut,
delete: httpDelete,
};
};
// 从响应中解析出数据
export const unwrapResponse = <T>(resp: HttpResponse<T>) => {
const [err, data, result] = resp;
// 如果 err 存在说明有 http 错误,那么直接抛出错误,
// 如果没有 err那么接下来判断是否存在业务错误。
// 如果 result 中 isSuccess 为 false说明有业务错误
// 如果 result 不存在,说明 retRaw 为 true是直接返回 data 的情况。
if (err) throw err;
// 不能判断 !result 从而提前结束分支,
// 因为当 retRaw 为 true 时result 总是为 null。
if (result) {
const { isSuccess, path, msg, errorMsg } = result;
if (!isSuccess) throw new Error(`${path ? `${path}: ` : ''}${msg || errorMsg || '请求失败'}`);
}
// 不做严格判空null == undefined -> true
if (data == null) throw new Error('响应数据为空');
return data;
};
// 从错误中解析出错误信息
export const parseErrorFeedback = (error: Error) => {
// 当发生 http 错误时unwrapResponse 会直接抛出错误,
// 所以如果不是 AxiosError说明是业务错误直接返回错误信息。
if (!isAxiosError(error)) {
return error.message;
}
// 如果是 AxiosError说明是 http 错误,尝试获取 result
const result = error.response?.data;
// 如果 result 不存在,返回 error 中原生的 code + message
if (!result) {
const { code, message } = error;
return `${code ? `code: ${code}, ` : ''}${message ? `message: ${message}` : ''}`;
}
// 如果 result 存在,判断是否是字符串,
// 如果是字符串,说明是一些奇怪的、直接返回的错误信息,直接返回;
// 如果不是字符串,说明是 Result 类型,返回其中的 path + msg + errorMsg。
if (typeof result === 'string') {
return result;
}
const { path, msg, errorMsg } = result as Result;
return `${path ? `${path}: ` : ''}${msg || errorMsg || '请求失败'}`;
};