JavaScript 的 fetch()
API 提供了一种现代、易用的接口,用于从服务器异步获取资源。然而,在实际项目中,我们通常需要对基础的 fetch()
进行封装,以实现诸如添加默认配置、处理响应状态、自动解析响应数据、以及处理认证令牌等常见需求。
这个代码是一个比较基本的情况,你需要根据你自己项目的特点来对他进行一些适当的修改,比如,引入你所使用组件库的 Message
组件来显示提示或错误信息。或者根据后端的返回的数据结构来修改这里对数据的解析和对异常的处理。
讲解
定义参数类型
由于是 TypeScript
编写的,我们需要对它的 HttpMethod 和 ResponseType 参数进行类型的规制。
因此,我们要:
type HttpMethod = "GET" | "POST" | "PUT" | "DELETE" | "PATCH";
type ResponseType = "json" | "text" | "blob" | "arrayBuffer" | "formData";
获取 Token 令牌
我们把 getToken()
封装成一个函数,由于不同的项目获得 Token
的方式不一样,因此这里的 getToken()
函数要由用户自己实现。
/**
* 获取当前用户的认证令牌。
* @returns 返回当前用户的认证令牌,如果没有认证令牌则返回 null。
*/
const getToken = (): string | null => {
// 获取 token 的代码根据需求来写。。。
};
封装的核心函数
http()
函数是整个封装的核心,它定义了函数的参数类型,以及如何处理响应状态、处理认证令牌等。
export async function http(
method: HttpMethod,
url: string,
data?: any,
responseType: ResponseType = "json",
headers?: HeadersInit,
noToken: boolean = false
): Promise<any> {
// 内容。。。
}
定义 BASE_URL 和 Token
这里的 BASE_URL
是写死的,因为是个 demo 的原因,你可以自己写在配置文件并引入进来。
const BASE_URL = "/api";
const AUTH_TOKEN = noToken ? null : getToken();
添加默认配置
/**
* 配置网络请求的初始化参数
* @param method 请求方法(如GET、POST等)
* @param headers 请求头信息,可以是预设的或用户自定义的
* @param data 请求体数据,如果有的话
* @param AUTH_TOKEN 认证令牌,如果存在则会添加到请求头中
* @returns 返回一个配置好的 RequestInit 对象,可用于fetch等网络请求中
*/
const config: RequestInit = {
method,
headers: new Headers({
"Content-Type": "application/json; charset=utf-8",
...headers,
...(AUTH_TOKEN ? { Authorization: `Bearer ${AUTH_TOKEN}` } : {}),
}),
...(data ? { body: data } : {}),
};
序列化数据。
为了保证发送数据时候的准确性,当 data 类型是对象的时候,我们会序列号一下确保正确。
// 如果请求方法需要数据并且数据是对象类型,则序列化数据。
if (["POST", "PUT", "PATCH"].includes(method) && typeof data === "object") {
config.body = JSON.stringify(data);
}
开始请求
try {
以 try
语句开始一个异常处理块,用于捕获在该块内部可能抛出的任何错误。
const response = await fetch(BASE_URL + url, config);
使用 await
关键字等待 fetch
函数的异步执行结果。fetch 函数发起一个到指定 URL(由 BASE_URL
和 url
拼接而成)的 HTTP 请求,并接受一个配置对象 config
作为参数,用于设置请求头、方法等信息。请求完成后,返回一个 Response
对象赋值给常量 response
。
if (!response.ok) {
throw new Error(`HTTP error! status: ${response.status}`);
}
检查 response
对象的 ok
属性(fetch()
API 自带的)。若其值为 false
(表示 HTTP
状态码不在 2xx
范围内,即请求未成功),则抛出一个新的 Error
对象,其中包含关于 HTTP
错误的自定义消息,包括具体的错误状态码。
switch (responseType) {
case "json":
return response.json();
case "text":
return response.text();
case "blob":
return response.blob();
case "arrayBuffer":
return response.arrayBuffer();
case "formData":
return response.formData();
default:
throw new Error("Unsupported response type");
}
使用 switch
语句根据传入的 responseType
变量进行分支判断。根据不同的值,调用 response
对象对应的解析方法来处理响应数据,并返回解析结果:
- json: 解析为 JSON 对象。
- text: 解析为纯文本字符串。
- blob: 解析为 Blob 对象(二进制大对象)。
- arrayBuffer: 解析为 ArrayBuffer 对象(用于表示通用的、固定长度的原始二进制数据缓冲区)。
- formData: 解析为 FormData 对象(用于存储键值对,通常用于发送表单数据)。
若responseType
值不属于上述任何情况,则默认分支抛出一个错误,提示不支持的响应类型。
} catch (error) {
console.error("There was a problem with the fetch operation:", error);
throw error;
}
在 catch
语句中捕获在 try
块中抛出的所有错误。首先,使用 console.error
将包含详细错误信息的消息输出到控制台。接着,重新抛出(throw error)同一错误,以便调用者能够继续处理或捕获此错误。
}
结束整个 try…catch 异常处理块。
完整代码
type HttpMethod = "GET" | "POST" | "PUT" | "DELETE" | "PATCH";
type ResponseType = "json" | "text" | "blob" | "arrayBuffer" | "formData";
/**
* 获取当前用户的认证令牌。
* @returns 返回当前用户的认证令牌,如果没有认证令牌则返回 null。
*/
const getToken = (): string | null => {
// 获取 token 的代码根据需求来写。。。
};
/**
* 执行HTTP请求的通用函数。
* @param method 请求方法,支持GET、POST、PUT、DELETE和PATCH。
* @param url 请求的URL。
* @param data 可选,请求的数据。
* @param responseType 可选,响应的数据类型,默认为"json"。支持的类型有"json"、"text"、"blob"、"arrayBuffer"和"formData"。
* @param headers 可选,请求头信息。
* @param noToken 可选,就算本地存储了 Token 也不携带,默认为 false。
* @returns 返回一个Promise,解析为请求结果。
*/
export async function http(
method: HttpMethod,
url: string,
data?: any,
responseType: ResponseType = "json",
headers?: HeadersInit,
noToken: boolean = false
): Promise<any> {
const BASE_URL = "/api";
const AUTH_TOKEN = noToken ? null : getToken();
/**
* 配置网络请求的初始化参数
* @param method 请求方法(如GET、POST等)
* @param headers 请求头信息,可以是预设的或用户自定义的
* @param data 请求体数据,如果有的话
* @param AUTH_TOKEN 认证令牌,如果存在则会添加到请求头中
* @returns 返回一个配置好的 RequestInit 对象,可用于fetch等网络请求中
*/
const config: RequestInit = {
method,
headers: new Headers({
"Content-Type": "application/json; charset=utf-8",
...headers,
...(AUTH_TOKEN ? { Authorization: `Bearer ${AUTH_TOKEN}` } : {}),
}),
...(data ? { body: data } : {}),
};
// 如果请求方法需要数据并且数据是对象类型,则序列化数据。
if (["POST", "PUT", "PATCH"].includes(method) && typeof data === "object") {
config.body = JSON.stringify(data);
}
try {
const response = await fetch(BASE_URL + url, config);
if (!response.ok) {
throw new Error(`HTTP error! status: ${response.status}`);
}
// 根据响应类型处理响应数据
switch (responseType) {
case "json":
return response.json();
case "text":
return response.text();
case "blob":
return response.blob();
case "arrayBuffer":
return response.arrayBuffer();
case "formData":
return response.formData();
default:
throw new Error("Unsupported response type");
}
} catch (error) {
console.error("There was a problem with the fetch operation:", error);
throw error;
}
}