eagleye

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) {

// 未

 

posted on 2025-06-29 09:59  GoGrid  阅读(556)  评论(0)    收藏  举报

导航