Files
ndm-web-platform/src/utils/http-client.ts

175 lines
6.3 KiB
TypeScript
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
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 unwrapVoidResponse = (resp: HttpResponse<void>) => {
const [err, , result] = resp;
if (err) throw err;
if (result) {
const { isSuccess, path, msg, errorMsg } = result;
if (!isSuccess) throw new Error(`${path ? `${path}: ` : ''}${msg || errorMsg || '请求失败'}`);
}
};
// 从错误中解析出错误信息
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 || '请求失败'}`;
};