fetch函数全面解析:从基础到企业级实践
fetch函数全面解析:从基础到企业级实践
fetch是现代 JavaScript 生态中处理网络请求的核心 API,凭借其基于 Promise 的简洁语法和强大功能,已逐步取代传统的XMLHttpRequest,成为 Web 开发的标准选择。本文将从基础概念、核心特性到企业级实践,全面解析fetch的使用与优化。
一、fetch是内置函数吗?
1.1 起源与标准化
fetch是现代浏览器和 JavaScript 运行环境的内置函数,其标准化进程如下:
|
时间 |
事件 |
说明 |
|
2015年 |
首次引入浏览器 |
作为浏览器原生 API 出现,替代传统的XMLHttpRequest |
|
2015年 |
纳入 WHATWG Fetch Living Standard |
成为国际标准,确保跨浏览器兼容性 |
|
2022年 |
Node.js 18+ 原生支持 |
无需额外库即可在 Node.js 中使用fetch |
|
2023年 |
Deno 1.0 内置支持 |
作为 Deno 的核心 API,开箱即用 |
1.2 环境支持情况
fetch已广泛支持主流开发环境:
|
环境 |
支持情况 |
说明 |
|
现代浏览器 |
✅ 完全支持 |
Chrome 42+、Firefox 39+、Safari 10.1+、Edge 14+ 及以上版本 |
|
Node.js |
✅ v18+ 原生支持 |
v18 前需通过node-fetch或undici库模拟 |
|
Deno |
✅ 内置支持 |
无需额外配置 |
|
Web Workers |
✅ 支持 |
包括 Service Workers 等 Worker 线程 |
|
React Native |
✅ 支持 |
基于平台原生实现(如 iOS 的 URLSession、Android 的 OkHttp) |
二、fetch核心特性详解
2.1 基本语法
fetch基于 Promise,返回一个解析为Response对象的 Promise:
const response = await fetch(resource, options);
2.2 参数解析
|
参数 |
类型 |
说明 |
示例 |
|
resource |
string/URL/Request |
请求的资源地址或Request对象 |
'/api/users'或new URL('https://api.example.com') |
|
options |
Object |
可选配置项(如请求方法、头信息、请求体等) |
{ method: 'POST', headers: {...} } |
2.3 常用配置选项
options支持丰富的配置,以下是常见参数:
{
method: 'GET', // 请求方法(GET/POST/PUT/DELETE 等)
headers: { // 请求头对象
'Content-Type': 'application/json',
'Authorization': 'Bearer token'
},
body: JSON.stringify(data), // 请求体(仅 POST/PUT 等非 GET 请求有效)
cache: 'no-cache', // 缓存策略(`default`/`no-store`/`reload` 等)
credentials: 'include', // 跨域时是否携带 Cookie(`omit`/`same-origin`/`include`)
mode: 'cors', // 跨域模式(`cors`/`no-cors`/`same-origin`)
redirect: 'follow', // 重定向处理(`follow`/`error`/`manual`)
signal: abortController.signal // 中止请求的信号(通过 `AbortController` 生成)
}
2.4Response对象详解
fetch返回的Response对象包含响应的核心信息与方法:
|
属性/方法 |
类型 |
说明 |
|
response.ok |
boolean |
状态码在 200-299 时为true |
|
response.status |
number |
HTTP 状态码(如 200、404、500) |
|
response.statusText |
string |
状态文本(如 "OK"、"Not Found") |
|
response.headers |
Headers |
响应头对象(可通过get()方法获取具体头信息) |
|
response.url |
string |
最终请求的 URL(处理重定向后) |
|
response.json() |
Promise<any> |
解析响应体为 JSON 对象 |
|
response.text() |
Promise<string> |
解析响应体为纯文本 |
|
response.blob() |
Promise<Blob> |
解析响应体为二进制大对象(适用于文件下载) |
|
response.arrayBuffer() |
Promise<ArrayBuffer> |
解析响应体为二进制数组缓冲区(适用于底层数据处理) |
|
response.clone() |
Response |
克隆响应对象(解决响应体只能读取一次的问题) |
三、企业级使用模式
3.1 基本数据获取
async function fetchUsers() {
try {
const response = await fetch('/api/users');
// 检查响应状态
if (!response.ok) {
throw new Error(`HTTP 错误!状态码:${response.status}`);
}
// 解析 JSON 数据
const users = await response.json();
return users;
} catch (error) {
console.error('请求失败:', error);
throw error; // 向上传递错误
}
}
3.2 带参数的 GET 请求
通过URLSearchParams或URL对象构造带查询参数的 URL:
async function searchProducts(query: string, limit = 10) {
// 构造 URL 对象
const url = new URL('/api/products', window.location.origin);
// 添加查询参数
url.searchParams.append('q', query);
url.searchParams.append('limit', limit.toString());
const response = await fetch(url);
return response.json();
}
3.3 POST 请求提交数据
interface User {
name: string;
email: string;
}
async function createUser(userData: User) {
const response = await fetch('/api/users', {
method: 'POST',
headers: {
'Content-Type': 'application/json',
'Authorization': `Bearer ${localStorage.getItem('token')}` // 携带认证令牌
},
body: JSON.stringify(userData) // 序列化请求体
});
if (!response.ok) {
const errorData = await response.json();
throw new Error(`创建用户失败:${errorData.message}`);
}
return response.json(); // 返回创建的用户数据
}
3.4 文件上传
使用FormData对象处理文件上传:
async function uploadFile(file: File) {
const formData = new FormData();
formData.append('file', file); // 添加文件
formData.append('metadata', JSON.stringify({ // 添加元数据
uploader: 'user123',
timestamp: new Date().toISOString()
}));
const response = await fetch('/api/upload', {
method: 'POST',
body: formData
// 注意:无需手动设置 Content-Type,浏览器会自动设置为 multipart/form-data
});
return response.json();
}
3.5 请求超时控制
通过AbortController实现请求超时:
async function fetchWithTimeout(resource, options = {}, timeout = 8000) {
const controller = new AbortController();
const timeoutId = setTimeout(() => controller.abort(), timeout); // 设置超时时间
try {
const response = await fetch(resource, {
...options,
signal: controller.signal // 关联中止信号
});
clearTimeout(timeoutId); // 成功响应后清除超时
return response;
} catch (error) {
clearTimeout(timeoutId);
if (error.name === 'AbortError') {
throw new Error('请求超时');
}
throw error; // 其他错误
}
}
四、TypeScript 类型增强
4.1 响应类型安全
通过类型断言或泛型函数确保响应数据的类型安全:
interface User {
id: number;
name: string;
email: string;
}
async function fetchUser(userId: number): Promise<User> {
const response = await fetch(`/api/users/${userId}`);
if (!response.ok) {
throw new Error(`获取用户 ${userId} 失败`);
}
// 类型断言确保返回值为 User 类型
return response.json() as Promise<User>;
}
4.2 类型安全的fetch封装
通过泛型封装通用fetch函数,提升复用性:
async function typedFetch<T>(
url: string,
options?: RequestInit
): Promise<T> {
const response = await fetch(url, options);
if (!response.ok) {
const errorData = await response.json();
throw new Error(errorData.message || '请求失败');
}
return response.json() as Promise<T>;
}
// 使用示例
interface Product {
id: string;
name: string;
price: number;
}
const product = await typedFetch<Product>('/api/products/123');
五、与XMLHttpRequest的对比
|
特性 |
fetch |
XMLHttpRequest |
|
语法 |
基于 Promise(异步更简洁) |
基于事件回调(代码嵌套复杂) |
|
流式处理 |
✅ 支持(通过ReadableStream) |
❌ 不支持 |
|
请求中止 |
✅ 通过AbortController |
✅ 通过abort()方法 |
|
CORS 处理 |
✅ 配置简单(mode/credentials) |
�� 需手动处理预请求(OPTIONS) |
|
进度监控 |
❌ 原生不支持(需结合ReadableStream) |
✅ 支持progress事件 |
|
超时设置 |
❌ 原生不支持(需结合AbortController) |
✅ 支持timeout属性 |
|
JSON 处理 |
✅ 内置response.json()方法 |
❌ 需手动JSON.parse() |
|
请求缓存 |
✅ 支持精细控制(cache配置) |
�� 仅支持简单缓存策略 |
|
现代特性 |
✅ 支持 Service Workers 等 |
❌ 不支持 |
六、企业级最佳实践
6.1 创建 API 客户端抽象层
通过封装fetch实现统一的 API 调用逻辑,处理认证、错误等通用需求:
class ApiClient {
private baseUrl: string;
constructor(baseUrl: string) {
this.baseUrl = baseUrl;
}
// GET 请求
async get<T>(endpoint: string): Promise<T> {
return this.request<T>('GET', endpoint);
}
// POST 请求
async post<T>(endpoint: string, data: any): Promise<T> {
return this.request<T>('POST', endpoint, data);
}
// 通用请求方法
private async request<T>(
method: string,
endpoint: string,
data?: any
): Promise<T> {
const url = `${this.baseUrl}${endpoint}`;
const headers: Record<string, string> = {
'Content-Type': 'application/json'
};
// 添加认证令牌
const token = localStorage.getItem('authToken');
if (token) {
headers['Authorization'] = `Bearer ${token}`;
}
const response = await fetch(url, {
method,
headers,
body: data ? JSON.stringify(data) : undefined
});
if (!response.ok) {
await this.handleError(response); // 统一错误处理
}
return response.json() as Promise<T>;
}
// 错误处理逻辑
private async handleError(response: Response) {
let errorMessage = `HTTP 错误 ${response.status}`;
try {
const errorData = await response.json();
if (errorData.message) {
errorMessage = errorData.message;
}
} catch {
// 响应体非 JSON 时,直接使用状态码
}
throw new Error(errorMessage);
}
}
// 使用示例
const api = new ApiClient('https://api.example.com');
const users = await api.get<User[]>('/users'); // 获取用户列表
const newUser = await api.post<User>('/users', { name: 'Alice' }); // 创建用户
6.2 拦截器模式
通过请求/响应拦截器实现全局逻辑(如日志、认证、错误重定向):
// 请求拦截器:添加认证令牌和请求 ID
async function interceptRequest(input: RequestInfo, init?: RequestInit) {
let url = typeof input === 'string' ? input : input.url;
const options = init || {};
// 添加认证令牌
const token = localStorage.getItem('authToken');
if (token) {
options.headers = {
...options.headers,
Authorization: `Bearer ${token}`
};
}
// 添加请求 ID(用于日志追踪)
options.headers = {
...options.headers,
'X-Request-ID': crypto.randomUUID()
};
return { url, options };
}
// 响应拦截器:处理未授权、服务器错误等
async function interceptResponse(response: Response) {
if (response.status === 401) {
// 未授权时跳转登录页
window.location.href = '/login';
return response;
}
if (response.status >= 500) {
// 记录服务器错误日志
console.error('服务器错误:', response);
}
return response;
}
// 封装带拦截器的 fetch
const fetchWithInterceptors = async (input: RequestInfo, init?: RequestInit) => {
const { url, options } = await interceptRequest(input, init);
const response = await fetch(url, options);
return interceptResponse(response);
};
6.3 缓存策略实现
通过内存缓存减少重复请求,提升性能:
const apiCache = new Map<string, { data: any; timestamp: number }>();
async function fetchWithCache<T>(
url: string,
options?: RequestInit,
cacheTime = 60000 // 缓存 1 分钟
): Promise<T> {
const cacheKey = `${url}-${JSON.stringify(options)}`;
// 检查缓存是否有效
const cached = apiCache.get(cacheKey);
if (cached && Date.now() - cached.timestamp < cacheTime) {
return cached.data as T;
}
// 无有效缓存时发送请求
const response = await fetch(url, options);
if (!response.ok) {
throw new Error(`请求失败:${response.status}`);
}
const data = await response.json();
// 更新缓存
apiCache.set(cacheKey, {
data,
timestamp: Date.now()
});
return data as T;
}
七、常见问题与解决方案
7.1 CORS 问题
问题:跨域请求被浏览器阻止,控制台提示“CORS 策略阻止”。
解决方案:
- 前端配置:明确设置mode: 'cors'和credentials: 'include'(如需携带 Cookie)。
- 后端配置:返回以下响应头:Access-Control-Allow-Origin: https://yourdomain.com // 允许的源
Access-Control-Allow-Methods: GET, POST, OPTIONS // 允许的方法
Access-Control-Allow-Headers: Content-Type, Authorization // 允许的请求头
Access-Control-Allow-Credentials: true // 允许携带 Cookie
7.2 错误处理不完整
问题:仅检查response.ok忽略特定状态码(如 401 未授权、403 禁止访问)。
解决方案:针对关键状态码添加逻辑:
async function safeFetch(url: string, options?: RequestInit) {
const response = await fetch(url, options);
if (response.status === 401) {
// 未
浙公网安备 33010602011771号