简单的 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("&");
}