fetch 函数封装
更新: 2025/3/13 17:45:22 字数: 0 字
fetch 函数封装
ts
import {getTokenUtil} from "@/utils/tokenUtil";
import {getModalSystemConfig, globalMessageApi, globalModalApi} from "@/components/OpComponent/Message";
import store from "@/store";
import {AsyncUserLogout} from "@/store/modules/userSlice.ts";
/*请求类型*/
export enum RequestMethod {
GET = 'get',
POST = 'post',
PUT = 'put',
DELETE = 'delete',
PATCH = 'patch',
}
type PrFetchOptions = {
baseURL?: string;
timeout?: number;
method?: string, // 请求方式,默认是get,可以在这里设置默认方式为其他请求方式
headers?: {
[key: string]: string
},
}
type Function = (...args: any[]) => any;
class Request {
baseURL: string;
timeout: number | null;
_method: string;
_headers: object;
private requestInterceptors: Array<[Function, Function]> = [];
private responseInterceptors: Array<[Function, Function]> = [];
constructor(baseURL: string, timeout: number | null, method: string, headers: object) {
this.baseURL = baseURL || "";
this.timeout = timeout || null;
this._method = method || 'get';
this._headers = headers || {};
}
// 添加请求拦截器
public interceptors = {
request: {
use: (onFulfilled: Function, onRejected: Function) => {
this.requestInterceptors.push([onFulfilled, onRejected]);
}
},
response: {
use: (onFulfilled: Function, onRejected: Function) => {
this.responseInterceptors.push([onFulfilled, onRejected]);
}
}
};
/**
* 检查 HTTP 状态码
* @param response
*/
checkHttpStatus = (response: Response) => {
const {status: statusCode, statusText, url} = response;
if (statusCode >= 200 && statusCode < 300) {
return response;
}
const msg = `请求错误: ${statusCode}[${statusText}]`;
return Promise.reject({
url,
msg,
code: statusCode,
});
};
/**
* 发送请求
* @param url
* @param options 发送请求之前的配置
*/
async fetch(url: string, options: any): Promise<any> {
let config = {
...options,
url: this.baseURL + url,
method: options.method || this._method,
headers: {
...this._headers,
...options.headers,
},
params: options.params || null,
body: options.data || null,
};
if (options.data instanceof FormData) {
config.body = options.data
delete config.headers['Content-Type']
}
if (config.headers["Content-Type"] == "application/json") {
config.body = JSON.stringify(options.data) || null
}
// 如果有 params 对 其镜像拼接,加在 url 中, 但是值不能是 null 和 undefined
if (options.params) {
for (const key in options.params) {
if (options.params[key] === null || options.params[key] === undefined) {
delete options.params[key];
}
}
}
if (options.params) {
const params = new URLSearchParams(options.params).toString();
config.url += '?' + params;
}
const controller = new AbortController();
const hasTimeOut: boolean = !options.isTimeout;
if (hasTimeOut) {
config.timeout = options.timeout || this.timeout;
}
// 应用请求拦截器
for (const [onFulfilled, onRejected] of this.requestInterceptors) {
try {
// 构建好请求配置
config = await onFulfilled(config);
} catch (error) {
return onRejected(error);
}
}
try {
const result =
await (config.hasTimeOut ?
Promise.race([
fetch(config.url,
{...config, signal: controller.signal}),
new Promise((_, reject) =>
setTimeout(() => {
controller.abort();
reject(new Error("request time out"))
}, config.timeout))
]) : fetch(config.url, config))
.then(response => response as Response)
.then(this.checkHttpStatus)
let data = await result.json();
// 应用响应拦截器
for (const [onFulfilled, onRejected] of this.responseInterceptors) {
try {
data = await onFulfilled(data);
} catch (error) {
// 这里就是返回失败,直接抛出,不再走拦截器,后面会被 catch 捕获
return Promise.reject("失败");
}
}
return data;
} catch (error) {
console.log("error========", error)
// 处理请求错误
for (const [, onRejected] of this.responseInterceptors) {
// eslint-disable-next-line no-useless-catch
try {
return await onRejected(error);
} catch (e) {
// 如果所有拦截器都处理失败,抛出最终错误
throw e;
}
}
throw error;
}
}
}
/**
* 创建请求,希望可以将其封装成一个通用请求方法
* @param option
*/
function createPrFetch(option: PrFetchOptions) {
// 创建请求实例对象
const requestInstance = new Request(option.baseURL || "", option.timeout || null, option.method || "get", option.headers || {});
return {
fetch: (options: any) => requestInstance.fetch(options.url, options),
interceptors: requestInstance.interceptors,
}
}
export const isReLogin = {
show: false
}
// 配置基础 URL 和超时时间,创建 request 实例
const request = createPrFetch({
baseURL: '/pro-api',
timeout: 10000,
headers: {
"Content-Type": "application/json",
},
});
// 配置请求拦截器
request.interceptors.request.use(
(config: any) => {
// 配置 token
if (!config.hasToken) {
config.headers.Authorization = 'Bearer ' + getTokenUtil();
}
return config;
},
(error: any) => {
return Promise.reject(error);
}
);
// 配置响应拦截器
request.interceptors.response.use(
async (response: any) => {
// 进行 code | msg 统一处理
const code = response.code;
const msg = response.msg || "未知错误";
console.log("response", response)
if (code === 500) {
globalMessageApi.error(msg)
console.log("未知错误")
return Promise.reject(response);
} else if (code === 401) {
if (!isReLogin.show) {
globalMessageApi.error("登录状态已过期,请重新登录")
isReLogin.show = true;
try {
globalModalApi.confirm(getModalSystemConfig("登录状态已过期,请重新登录")).then((res: any) => {
if (res) {
store.dispatch(AsyncUserLogout()).finally(() => {
// 路由到 /login 页面
// router.navigate("/login");
isReLogin.show = false;
})
}
})
} catch (e) {
console.log(e)
}
}
} else if (code !== 200) {
globalMessageApi.error(response.msg)
return Promise.reject(response);
}
return response;
},
(error: any) => {
console.log("error——————————", error)
let { msg} = error;
if (msg == "Network Error") {
msg = "后端接口连接异常";
} else if (msg.includes("timeout") || msg.includes("Timeout")) {
msg = "系统接口请求超时";
} else if (msg.includes("Request failed with status code")) {
msg = "系统接口" + msg.substr(msg.length - 3) + "异常";
}
// ElMessage({ message: message, type: 'error', duration: 5 * 1000 })
// message.error()
globalMessageApi.error(msg)
return Promise.reject(error);
}
);
/*
* TODO 额外对外暴露方法,在调用 fetch 之前可以链式调用 requestHandler 去个性化配置请求,从而再次进行请求
* 主要用于个性化配置
* */
export default request.fetch;