fetch封装

536 阅读3分钟

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);
  });
};

结尾

原文链接 wp-boke.work/blog-detail…