Fetch API
Fetch API 提供了现代化的网络请求接口,是 XMLHttpRequest 的替代方案。
📚 目录
1. Fetch 概述
1.1 什么是 Fetch
Fetch API 提供了用于获取资源的接口,包括跨网络异步获取资源的能力。
特点:
- 基于 Promise,支持 async/await
- 更简洁的 API
- 更好的错误处理
- 支持流式处理
1.2 浏览器支持
// 检查支持
if (window.fetch) {
// 支持 Fetch API
} else {
// 需要 polyfill 或使用 XMLHttpRequest
}2. 基本用法
2.1 GET 请求
// 最简单的 GET 请求
fetch('https://api.example.com/data')
.then(response => response.json())
.then(data => {
console.log(data);
})
.catch(error => {
console.error('Error:', error);
});
// 使用 async/await
async function fetchData() {
try {
const response = await fetch('https://api.example.com/data');
const data = await response.json();
console.log(data);
} catch (error) {
console.error('Error:', error);
}
}2.2 POST 请求
// POST 请求(JSON 数据)
fetch('https://api.example.com/data', {
method: 'POST',
headers: {
'Content-Type': 'application/json',
},
body: JSON.stringify({
name: 'John',
age: 30
})
})
.then(response => response.json())
.then(data => console.log(data));
// POST 请求(FormData)
const formData = new FormData();
formData.append('name', 'John');
formData.append('age', '30');
fetch('https://api.example.com/data', {
method: 'POST',
body: formData
})
.then(response => response.json())
.then(data => console.log(data));2.3 其他 HTTP 方法
// PUT
fetch('https://api.example.com/data/1', {
method: 'PUT',
headers: {
'Content-Type': 'application/json',
},
body: JSON.stringify({ name: 'Updated' })
});
// DELETE
fetch('https://api.example.com/data/1', {
method: 'DELETE'
});
// PATCH
fetch('https://api.example.com/data/1', {
method: 'PATCH',
headers: {
'Content-Type': 'application/json',
},
body: JSON.stringify({ name: 'Patched' })
});3. Request 对象
3.1 创建 Request 对象
// 方式 1:直接使用 URL
fetch('https://api.example.com/data');
// 方式 2:使用 Request 对象
const request = new Request('https://api.example.com/data', {
method: 'POST',
headers: {
'Content-Type': 'application/json',
},
body: JSON.stringify({ data: 'value' })
});
fetch(request);3.2 Request 属性
const request = new Request('https://api.example.com/data', {
method: 'POST',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify({ data: 'value' })
});
request.url; // "https://api.example.com/data"
request.method; // "POST"
request.headers; // Headers 对象
request.body; // ReadableStream
request.bodyUsed; // false(是否已读取)3.3 克隆 Request
const request = new Request('https://api.example.com/data');
// 克隆 Request(body 可重复使用)
const clonedRequest = request.clone();
// ⚠️ 注意:Request 的 body 只能读取一次
// 如果已经读取过,需要克隆才能再次使用4. Response 对象
4.1 Response 属性
fetch('https://api.example.com/data')
.then(response => {
// 状态码
response.status; // 200
response.statusText; // "OK"
// 响应头
response.headers; // Headers 对象
response.ok; // true(status 200-299)
response.redirected; // false(是否重定向)
response.type; // "basic", "cors", "error" 等
response.url; // 最终 URL(可能经过重定向)
// 响应体
response.body; // ReadableStream
response.bodyUsed; // false(是否已读取)
});4.2 读取响应体
fetch('https://api.example.com/data')
.then(response => {
// JSON
return response.json();
})
.then(data => console.log(data));
// 其他格式
response.text(); // 文本
response.blob(); // Blob
response.arrayBuffer(); // ArrayBuffer
response.formData(); // FormData4.3 检查响应状态
fetch('https://api.example.com/data')
.then(response => {
if (!response.ok) {
throw new Error(`HTTP error! status: ${response.status}`);
}
return response.json();
})
.then(data => console.log(data))
.catch(error => console.error('Error:', error));4.4 处理不同响应类型
async function fetchData() {
const response = await fetch('https://api.example.com/data');
const contentType = response.headers.get('content-type');
if (contentType && contentType.includes('application/json')) {
return await response.json();
} else if (contentType && contentType.includes('text/html')) {
return await response.text();
} else {
return await response.blob();
}
}5. 请求配置
5.1 Headers
// 方式 1:对象形式
fetch('https://api.example.com/data', {
headers: {
'Content-Type': 'application/json',
'Authorization': 'Bearer token123'
}
});
// 方式 2:Headers 对象
const headers = new Headers();
headers.append('Content-Type', 'application/json');
headers.append('Authorization', 'Bearer token123');
fetch('https://api.example.com/data', {
headers: headers
});
// Headers 操作
headers.get('Content-Type'); // "application/json"
headers.has('Authorization'); // true
headers.set('X-Custom', 'value'); // 设置
headers.delete('Authorization'); // 删除
headers.forEach((value, key) => {
console.log(key, value);
});5.2 请求模式
fetch('https://api.example.com/data', {
mode: 'cors', // 'cors' | 'no-cors' | 'same-origin' | 'navigate'
});
// cors - 跨域请求(默认)
// no-cors - 不跨域请求(有限响应)
// same-origin - 同源请求
// navigate - 导航请求5.3 凭证
fetch('https://api.example.com/data', {
credentials: 'include', // 'omit' | 'same-origin' | 'include'
});
// omit - 不发送凭证
// same-origin - 同源时发送凭证
// include - 总是发送凭证(Cookie)5.4 缓存控制
fetch('https://api.example.com/data', {
cache: 'no-cache', // 'default' | 'no-store' | 'reload' | 'no-cache' | 'force-cache' | 'only-if-cached'
});
// default - 浏览器默认缓存策略
// no-store - 不缓存
// reload - 强制重新获取
// no-cache - 每次验证缓存
// force-cache - 强制使用缓存
// only-if-cached - 只使用缓存5.5 重定向
fetch('https://api.example.com/data', {
redirect: 'follow', // 'follow' | 'error' | 'manual'
});
// follow - 自动跟随重定向(默认)
// error - 重定向时抛出错误
// manual - 手动处理重定向6. 错误处理
6.1 网络错误
fetch('https://api.example.com/data')
.then(response => {
if (!response.ok) {
throw new Error(`HTTP error! status: ${response.status}`);
}
return response.json();
})
.catch(error => {
// 网络错误或 HTTP 错误
console.error('Fetch error:', error);
});6.2 超时处理
// 使用 AbortController 实现超时
function fetchWithTimeout(url, options = {}, timeout = 5000) {
const controller = new AbortController();
const timeoutId = setTimeout(() => controller.abort(), timeout);
return fetch(url, {
...options,
signal: controller.signal
})
.then(response => {
clearTimeout(timeoutId);
return response;
})
.catch(error => {
clearTimeout(timeoutId);
if (error.name === 'AbortError') {
throw new Error('Request timeout');
}
throw error;
});
}
// 使用
fetchWithTimeout('https://api.example.com/data', {}, 5000)
.then(response => response.json())
.catch(error => console.error('Error:', error));6.3 取消请求
// 使用 AbortController 取消请求
const controller = new AbortController();
fetch('https://api.example.com/data', {
signal: controller.signal
})
.then(response => response.json())
.catch(error => {
if (error.name === 'AbortError') {
console.log('Request aborted');
}
});
// 取消请求
controller.abort();7. 高级用法
7.1 流式处理
// 流式读取响应
fetch('https://api.example.com/data')
.then(response => {
const reader = response.body.getReader();
const decoder = new TextDecoder();
function readChunk() {
return reader.read().then(({ done, value }) => {
if (done) {
return;
}
const chunk = decoder.decode(value);
console.log('Chunk:', chunk);
return readChunk();
});
}
return readChunk();
});7.2 上传进度
// ⚠️ Fetch API 不支持上传进度
// 需要使用 XMLHttpRequest 或 fetch with ReadableStream
// 使用 XMLHttpRequest 获取上传进度
const xhr = new XMLHttpRequest();
xhr.upload.addEventListener('progress', (event) => {
if (event.lengthComputable) {
const percentComplete = (event.loaded / event.total) * 100;
console.log('Upload progress:', percentComplete);
}
});7.3 下载进度
// 使用 Response.body 获取下载进度
async function fetchWithProgress(url, onProgress) {
const response = await fetch(url);
const reader = response.body.getReader();
const contentLength = +response.headers.get('Content-Length');
let receivedLength = 0;
const chunks = [];
while (true) {
const { done, value } = await reader.read();
if (done) break;
chunks.push(value);
receivedLength += value.length;
if (onProgress) {
onProgress(receivedLength, contentLength);
}
}
const allChunks = new Uint8Array(receivedLength);
let position = 0;
for (const chunk of chunks) {
allChunks.set(chunk, position);
position += chunk.length;
}
return new Blob([allChunks]);
}
// 使用
fetchWithProgress('https://example.com/large-file', (loaded, total) => {
console.log(`Progress: ${(loaded / total * 100).toFixed(2)}%`);
});7.4 封装 Fetch 工具函数
class FetchClient {
constructor(baseURL, defaultOptions = {}) {
this.baseURL = baseURL;
this.defaultOptions = defaultOptions;
}
async request(endpoint, options = {}) {
const url = `${this.baseURL}${endpoint}`;
const config = {
...this.defaultOptions,
...options,
headers: {
...this.defaultOptions.headers,
...options.headers,
},
};
try {
const response = await fetch(url, config);
if (!response.ok) {
throw new Error(`HTTP error! status: ${response.status}`);
}
const contentType = response.headers.get('content-type');
if (contentType && contentType.includes('application/json')) {
return await response.json();
}
return await response.text();
} catch (error) {
console.error('Fetch error:', error);
throw error;
}
}
get(endpoint, options = {}) {
return this.request(endpoint, { ...options, method: 'GET' });
}
post(endpoint, data, options = {}) {
return this.request(endpoint, {
...options,
method: 'POST',
body: JSON.stringify(data),
headers: {
'Content-Type': 'application/json',
...options.headers,
},
});
}
put(endpoint, data, options = {}) {
return this.request(endpoint, {
...options,
method: 'PUT',
body: JSON.stringify(data),
headers: {
'Content-Type': 'application/json',
...options.headers,
},
});
}
delete(endpoint, options = {}) {
return this.request(endpoint, { ...options, method: 'DELETE' });
}
}
// 使用
const api = new FetchClient('https://api.example.com', {
headers: {
'Authorization': 'Bearer token123'
}
});
api.get('/users').then(users => console.log(users));
api.post('/users', { name: 'John' }).then(user => console.log(user));8. 与 XMLHttpRequest 对比
8.1 主要区别
| 特性 | Fetch API | XMLHttpRequest |
|---|---|---|
| Promise 支持 | ✅ 原生支持 | ❌ 需要封装 |
| 请求取消 | ✅ AbortController | ✅ abort() |
| 上传进度 | ❌ 不支持 | ✅ 支持 |
| 下载进度 | ⚠️ 需要手动实现 | ✅ 支持 |
| 超时 | ⚠️ 需要手动实现 | ✅ 原生支持 |
| 浏览器支持 | 现代浏览器 | 所有浏览器 |
8.2 何时使用 XMLHttpRequest
- 需要上传/下载进度
- 需要支持旧浏览器
- 需要更细粒度的控制