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
解析响应体为 JSON 对象
response.text()
Promise
解析响应体为纯文本
response.blob()
Promise
解析响应体为二进制大对象(适用于文件下载)
response.arrayBuffer()
Promise
解析响应体为二进制数组缓冲区(适用于底层数据处理)
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
const response = await fetch(/api/users/${userId});
if (!response.ok) {
throw new Error(获取用户 ${userId} 失败);
}
// 类型断言确保返回值为 User 类型
return response.json() as Promise
}
4.2 类型安全的fetch封装
通过泛型封装通用fetch函数,提升复用性:
async function typedFetch
url: string,
options?: RequestInit
): Promise
const response = await fetch(url, options);
if (!response.ok) {
const errorData = await response.json();
throw new Error(errorData.message || '请求失败');
}
return response.json() as Promise
}
// 使用示例
interface Product {
id: string;
name: string;
price: number;
}
const product = await typedFetch
五、与XMLHttpRequest的对比
特性fetchXMLHttpRequest语法基于 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
return this.request
}
// POST 请求
async post
return this.request
}
// 通用请求方法
private async request
method: string,
endpoint: string,
data?: any
): Promise
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
}
// 错误处理逻辑
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
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
url: string,
options?: RequestInit,
cacheTime = 60000 // 缓存 1 分钟
): Promise
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 // 允许携带 Cookie7.2 错误处理不完整
问题:仅检查response.ok忽略特定状态码(如 401 未授权、403 禁止访问)。解决方案:针对关键状态码添加逻辑:
async function safeFetch(url: string, options?: RequestInit) {
const response = await fetch(url, options);
if (response.status === 401) {
// 未
}