Skip to content

简单的 http 封装

ts
type IHttpHeaders = Record<string, string>;
interface JSONType {
  [key: string]: string | number | boolean | null | undefined | JSONType;
}

export type HttpRequestDataType =
  | JSONType
  | FormData
  | undefined
  | Array<JSONType>;

interface IHttpOptions {
  method?: "POST" | "GET" | "PUT" | "DELETE";
  headers?: IHttpHeaders;
  onSuccess: (data: string) => void;
  onError: (error: HttpError) => void;
}

class HttpError extends Error {
  public status: number;
  public responseText?: string;
  constructor(options: {
    message?: string;
    status: number;
    responseText?: string;
  }) {
    super(options.message);
    this.status = options.status;
    this.responseText = options.responseText;
  }
}

export const request = (
  url: string,
  data: HttpRequestDataType,
  config: IHttpOptions
) => {
  const method =
    config.method && typeof config.method === "string"
      ? config.method.toLocaleUpperCase()
      : "GET";

  const http = new XMLHttpRequest();

  let url2 = url;
  if (method === "GET") {
    if (data instanceof FormData) {
      throw new Error("GET 的 data **不** 应该是 FormData");
    }
    const queryString = Array.isArray(data) ? "" : dataToQueryString(data);

    url2 = queryString ? url + "?" + queryString : url;
  }

  http.open(method, url2, true);

  const headers = combinationHeader(config.headers, method);

  // 除了 Content Type 需要单独处理,其他直接设置 Request Header
  let contentType = "";
  for (const key in headers) {
    if (key.toLocaleUpperCase() === "CONTENT-TYPE") {
      contentType = headers[key];
    } else {
      http.setRequestHeader(key, headers[key]);
    }
  }

  if (method === "POST" || method === "PUT") {
    const __data__ = data || {};

    const isFromData = data instanceof FormData;

    if (contentType === "application/json") {
      onPostByJson(http, __data__ as JSONType);
    } else if (contentType === "application/x-www-form-urlencoded") {
      onPostByUrlEncoded(http, __data__ as JSONType);
    } else if (contentType === "multipart/form-data") {
      if (isFromData) {
        onPostByFormData(http, data);
      } else {
        throw new Error("data 不是 FormData");
      }
    } else {
      throw new Error("未实现的 content type: " + contentType);
    }
  } else {
    http.send();
  }

  http.onreadystatechange = function () {
    // readyState
    // 0: 未初始化
    // 1: 已打开
    // 2: 已发送
    // 3: 接收中
    // 4: 完成

    if (http.readyState === 4) {
      if (http.status === 200) {
        config.onSuccess(http.responseText);
      } else {
        config.onError(
          new HttpError({
            message: "请求失败",
            status: http.status,
            responseText: http.responseText,
          })
        );
      }
    }
  };
};

function combinationHeader(header: IHttpHeaders = {}, method: string) {
  const defaultOptions: IHttpHeaders = {
    "Content-Type": "application/json",
  };

  let headers: Record<string, string> = {};

  if (method === "POST" || method === "PUT") {
    headers = { ...defaultOptions };
  }

  headers = { ...headers, ...header };

  return headers;
}

export const get = (
  url: string,
  data: JSONType | undefined,
  config: IHttpOptions
) => {
  return request(url, data, { ...config, method: "GET" });
};

export const post = (
  url: string,
  data: HttpRequestDataType,
  config: IHttpOptions
) => {
  return request(url, data, { ...config, method: "POST" });
};

// ------------------------------------------------------------

function onPostByJson(xhr: XMLHttpRequest, data: JSONType) {
  xhr.setRequestHeader("Content-Type", "application/json");
  const body = JSON.stringify(data);
  xhr.send(body);
}

function onPostByUrlEncoded(xhr: XMLHttpRequest, data: JSONType) {
  xhr.setRequestHeader("Content-Type", "application/x-www-form-urlencoded");

  const queryString = dataToQueryString(data);
  xhr.send(queryString);
}

function onPostByFormData(xhr: XMLHttpRequest, data: FormData) {
  if (!data) throw new Error("data is required");

  if (data instanceof FormData === false) {
    throw new Error("data must be a FormData instance");
  }

  xhr.setRequestHeader("Content-Type", "multipart/form-data");
  xhr.send(data);
}

function dataToQueryString(data?: JSONType): string {
  if (!data) {
    return "";
  }

  const params: string[] = [];

  for (const key in data) {
    const value = data[key];
    params.push(`${key}=${value}`);
  }

  return params.join("&");
}