fetch二次封装
每次新建项目都需要对请求进行封装,但是每个项目封装的方式都不一样,而且之前都是基于axios的二次封装,以下是我对fetch的二次封装,用作新建项目的基建api。
type FetchType = {
url: string;
method: keyof typeof HttpMethod; // HTTP请求方法类型
data?: any; // 请求数据
contentType?: keyof typeof ContentType; // 请求内容类型
headers?: HeadersInit; // 请求头
config?: RequestInit; // fetch配置项
timeout?: number; // 请求超时时间
};
const initConfig: any = {
env: process.env.NODE_ENV || "development", // 环境变量
baseUrlObj: {
development: "", // 开发环境的基础URL
production: "", // 生产环境的基础URL
},
contentType: "JSON", // 默认的请求内容类型
timeout: 5000, // 默认的请求超时时间
};
// 定义ContentType的enum
export const ContentType = {
JSON: "application/json",
FORM: "application/x-www-form-urlencoded",
TEXT: "text/plain",
BLOB: "application/octet-stream",
ARRAYBUFFER: "application/octet-stream",
};
// 定义method的enum
const HttpMethod = {
GET: "GET",
POST: "POST",
PUT: "PUT",
DELETE: "DELETE",
};
function fetchRequest({
url,
method,
data,
contentType = initConfig.contentType,
headers,
config,
timeout = initConfig.timeout,
}: FetchType) {
let options: { [key: string]: any } = {
method,
headers: {
"Content-Type": ContentType[contentType], // 设置请求头的Content-Type
...headers,
},
mode: "cors",
// 其他fetch配置项
...config,
};
if (!/^http(s?):\/\//i.test(url)) {
url = (initConfig.baseUrlObj[initConfig.env] || "") + url; // 拼接完整的请求URL
}
if (method !== "GET" && data) {
options.body =
contentType === "JSON" ? JSON.stringify(data) : new URLSearchParams(data); // 根据请求内容类型设置请求体数据
}
// 发送请求前调用请求拦截器
if (fetchRequest.requestInterceptor) {
options = fetchRequest.requestInterceptor(options);
}
let cancel;
// 取消请求配置
const cancelToken = new Promise((resolve) => {
cancel = () => resolve({ code: "END", msg: "Request termination" });
});
// 超时配置
const timeoutPromise = new Promise((_resolve, reject) => {
setTimeout(() => {
reject({
code: "ERROR",
msg: "请求超时",
});
}, timeout);
});
// 真实请求配置
const requestPromise = new Promise((resolve, reject) => {
fetch(url, options)
.then((response) => {
let newResponse;
// 处理响应前调用响应拦截器
if (fetchRequest.responseInterceptor) {
newResponse = fetchRequest.responseInterceptor(response, contentType);
}
resolve(newResponse);
})
.catch((error) => {
// 处理错误前调用错误处理函数
if (fetchRequest.errorHandler) {
fetchRequest.errorHandler(error);
}
reject(error);
});
});
// 返回一个包含请求和取消函数的对象
return {
request: Promise.race([cancelToken, timeoutPromise, requestPromise]),
cancel,
};
}
// 自定义请求拦截器
fetchRequest.requestInterceptor = async (options: any) => {
const { method, data, contentType } = options;
// 在这里可以对请求config进行处理,比如添加token等
if (method !== "GET" && data && contentType === "FORM") {
// 序列化表单数据
const formData: any = new URLSearchParams();
Object.entries(data).forEach(([key, value]) => {
formData.append(key, value);
});
// 更新请求体和请求头的配置
options.body = formData;
}
return options;
};
// 自定义响应拦截器
fetchRequest.responseInterceptor = async (
response: any,
contentType: keyof typeof ContentType
) => {
// 在这里可以对响应response进行处理,比如处理错误码等
let { status, statusText } = response;
if (status >= 200 && status < 400) {
let result;
switch (contentType) {
case "JSON":
result = response.json();
break;
case "TEXT":
result = response.text();
break;
case "BLOB":
result = response.blob();
break;
case "ARRAYBUFFER":
result = response.arrayBuffer();
break;
case "FORM": // 添加对 FORM 类型的处理
result = response.formData();
break;
default:
result = response;
}
return result;
}
return {
code: "STATUS ERROR",
status,
statusText,
};
};
// 自定义错误处理函数
fetchRequest.errorHandler = function (error: any) {
// 在这里可以对错误进行处理,比如弹窗提示等
console.log("请求发生错误:", error);
};
/* ———— 以下对不同请求又加了一层封装 —————————————————————————————————————————————————————————————— */
type Options = {
isCancel?: boolean;
config?: RequestInit;
headers?: HeadersInit;
contentType?: keyof typeof ContentType;
data?: any;
};
type GetFunction = (url: string, options?: Options) => Promise<any>;
type PostFunction = (url: string, options?: Options) => Promise<any>;
type PutFunction = (url: string, options?: Options) => Promise<any>;
type DeleteFunction = (url: string, options?: Options) => Promise<any>;
export const get: GetFunction = async (url: string, options: Options = {}) => {
if (options.isCancel) {
return fetchRequest({ url, method: "GET", ...options }); // 根据选项是否取消请求来调用fetchRequest
}
const fetchObj = fetchRequest({ url, method: "GET" });
return new Promise((resolve, reject) => {
fetchObj.request.then(resolve).catch(reject);
});
};
export const post: PostFunction = async (
url: string,
options: Options = {}
) => {
if (options.isCancel) {
return fetchRequest({ url, method: "POST", ...options });
}
const fetchObj = fetchRequest({ url, method: "POST", ...options });
return new Promise((resolve, reject) => {
fetchObj.request.then(resolve).catch(reject);
});
};
export const put: PutFunction = async (url: string, options: Options = {}) => {
if (options.isCancel) {
return fetchRequest({ url, method: "PUT", ...options });
}
const fetchObj = fetchRequest({ url, method: "PUT", ...options });
return new Promise((resolve, reject) => {
fetchObj.request.then(resolve).catch(reject);
});
};
export const del: DeleteFunction = async (
url: string,
options: Options = {}
) => {
if (options.isCancel) {
return fetchRequest({ url, method: "DELETE", ...options });
}
const fetchObj = fetchRequest({ url, method: "DELETE", ...options });
return new Promise((resolve, reject) => {
fetchObj.request.then(resolve).catch(reject);
});
};
结尾