一、Fetch 原生 API 介绍
1. Fetch API 简介
Fetch API 是现代浏览器提供的一种原生网络请求接口,用于替代传统的 XMLHttpRequest。它提供了一种更简洁、更灵活的方式来发送网络请求并处理响应。Fetch API 基于 Promise,使得异步操作处理更加优雅。
2. 基本用法
2.1. 基本语法
javascript
fetch(url, options)
.then(response => response.json())
.then(data => console.log(data))
.catch(error => console.error('Error:', error));
2.2. 简单示例
javascript
// 发送 GET 请求
fetch('https://api.example.com/data')
.then(response => {
// 检查响应状态
if (!response.ok) {
throw new Error('网络响应不正常');
}
return response.json();
})
.then(data => {
console.log('获取到的数据:', data);
})
.catch(error => {
console.error('获取数据出错:', error);
});
3. 请求配置选项
Fetch 的第二个参数是一个配置对象,用于自定义请求行为:
javascript
fetch(url, {
method: 'POST', // 请求方法: GET, POST, PUT, DELETE 等
headers: { // 请求头
'Content-Type': 'application/json',
'Authorization': 'Bearer token123'
},
body: JSON.stringify({ key: 'value' }), // 请求体
mode: 'cors', // 跨域模式: cors, no-cors, same-origin
credentials: 'same-origin', // 凭证: omit, same-origin, include
cache: 'no-cache', // 缓存控制
redirect: 'follow', // 重定向控制: follow, error, manual
referrer: 'client', // 来源
referrerPolicy: 'no-referrer-when-downgrade', // 引用策略
integrity: 'sha256-abcdef', // 子资源完整性校验
keepalive: false, // 请求在页面关闭后是否继续有效
signal: abortController.signal // 用于取消请求的信号
})
4. 处理响应
Fetch API 返回的 Response 对象提供了多种方法来处理不同类型的响应数据:
javascript
fetch('https://api.example.com/data')
.then(response => {
// 响应元数据
console.log(response.status); // 状态码 (例如 200, 404)
console.log(response.statusText); // 状态文本 (例如 "OK", "Not Found")
console.log(response.headers.get('Content-Type')); // 获取特定响应头
console.log(response.ok); // 如果状态码在 200-299 范围内则为 true
console.log(response.redirected); // 是否经过重定向
console.log(response.type); // 响应类型: basic, cors, error, opaque
console.log(response.url); // 响应的最终URL
// 根据内容类型选择不同的解析方法
// return response.json(); // 解析 JSON
// return response.text(); // 解析文本
// return response.blob(); // 解析二进制大对象
// return response.formData(); // 解析表单数据
// return response.arrayBuffer(); // 解析 ArrayBuffer
return response.json();
})
.then(data => {
console.log(data);
});
5. 不同类型的请求示例
5.1. POST 请求
javascript
fetch('https://api.example.com/submit', {
method: 'POST',
headers: {
'Content-Type': 'application/json'
},
body: JSON.stringify({
name: '张三',
email: '[email protected]'
})
})
.then(response => response.json())
.then(data => console.log('提交成功:', data))
.catch(error => console.error('提交失败:', error));
5.2. 上传文件
javascript
const formData = new FormData();
const fileField = document.querySelector('input[type="file"]');
formData.append('username', 'abc123');
formData.append('file', fileField.files[0]);
fetch('https://api.example.com/upload', {
method: 'POST',
body: formData
})
.then(response => response.json())
.then(data => console.log('上传成功:', data))
.catch(error => console.error('上传失败:', error));
6. 取消请求
使用 AbortController 可以取消正在进行的 fetch 请求:
javascript
const controller = new AbortController();
const signal = controller.signal;
fetch('https://api.example.com/longprocess', { signal })
.then(response => response.json())
.then(data => console.log(data))
.catch(error => {
if (error.name === 'AbortError') {
console.log('请求被取消');
} else {
console.error('请求出错:', error);
}
});
// 取消请求
setTimeout(() => controller.abort(), 5000); // 5秒后取消
7. 异步/等待写法
利用 async/await 语法可以让 Fetch API 的使用更加简洁优雅:
javascript
async function fetchData() {
try {
const response = await fetch('https://api.example.com/data');
if (!response.ok) {
throw new Error(`HTTP 错误: ${response.status}`);
}
const data = await response.json();
console.log('获取的数据:', data);
return data;
} catch (error) {
console.error('获取数据出错:', error);
}
}
// 调用函数
fetchData();
8. 跨域请求与 CORS
Fetch API 遵循同源策略,发起跨域请求时需要服务器端支持 CORS (跨域资源共享):
javascript
// 发起需要凭证的跨域请求
fetch('https://api.otherdomain.com/data', {
credentials: 'include', // 包含凭证(如cookies)
mode: 'cors' // 明确指定跨域模式
})
.then(response => response.json())
.then(data => console.log(data));
9. 比较 Fetch API、Axios 和 XMLHttpRequest
特性 | Fetch API | Axios | XMLHttpRequest |
---|---|---|---|
类型 | 浏览器原生API | 第三方库 | 浏览器原生API |
语法风格 | 基于Promise,简洁 | 基于Promise,更简洁易用 | 基于回调,较繁琐 |
自动JSON解析 | 需手动调用.json() | 自动处理 | 需手动处理 |
错误处理 | 不会自动捕获HTTP错误状态 | 自动捕获HTTP错误状态 | 需在回调中手动处理 |
请求取消 | 使用AbortController | 使用CancelToken或AbortController | abort()方法 |
超时设置 | 需自行实现 | 内置支持 | 内置支持 |
拦截器 | 不支持 | 支持请求和响应拦截器 | 不支持 |
下载进度 | 需自行通过流处理实现 | 内置支持 | 内置完整支持 |
跨域支持 | 原生支持CORS | 支持CORS | 需额外配置 |
并发请求 | 需使用Promise.all | 提供axios.all和axios.spread | 需手动管理多个实例 |
流式处理 | 支持 | 有限支持 | 不支持 |
同步请求 | 不支持 | 不支持 | 支持(但不推荐) |
浏览器兼容性 | 现代浏览器(IE不支持) | 广泛支持(内置polyfill) | 几乎所有浏览器 |
文件大小 | 原生(无需引入) | ~12KB min+gzip | 原生(无需引入) |
全局配置 | 不支持 | 支持 | 不支持 |
总结
Fetch API 作为一种现代化的网络请求方式,以其简洁的语法和强大的功能得到了广泛应用。它基于 Promise 的特性使得异步操作处理更加优雅,是前端开发中进行HTTP请求的重要选择。
虽然Axios等第三方库提供了更多开箱即用的功能,但 Fetch 作为浏览器原生API,不需要额外引入依赖,适合追求轻量级解决方案的项目。随着 Web 标准的发展,Fetch API 的功能也在不断完善,已经能够满足大多数应用场景的需求。
二、Fetch API 与 Web Streams API 的结合使用
1. 简介
在 Web 开发中,流式数据处理允许我们在完整响应到达之前就开始处理数据。这对于处理大型文件、实时数据或长时间运行的请求特别有用。Web Streams API 允许我们以流式方式处理数据,它提供了一种处理流数据的标准方式,包括:
ReadableStream
:可读取的数据流WritableStream
:可写入的数据流TransformStream
:可转换的数据流
Fetch API 的Response
对象提供了对底层数据流的访问,这使开发者可以:
- 在数据到达时逐块处理
- 减少内存使用(无需等待全部数据加载)
- 提高应用响应速度
- 支持实时数据处理
2. 使用Response.body读取流数据
2.1. 基本概念
Response.body
属性返回一个ReadableStream
对象,允许我们以流的方式访问响应体:
ini
fetch('https://api.example.com/large-data')
.then(response => {
const reader = response.body.getReader();
// 处理流数据...
});
2.2. 完整示例:流式处理文本数据
ini
async function streamText() {
const response = await fetch('https://api.example.com/stream');
const reader = response.body.getReader();
const decoder = new TextDecoder();
let result = '';
while (true) {
const { done, value } = await reader.read();
if (done) {
console.log('流读取完成');
break;
}
// 将Uint8Array转换为文本
const chunk = decoder.decode(value, { stream: true });
result += chunk;
console.log('收到数据块:', chunk);
// 可以在这里处理每个数据块
}
return result;
}
streamText().then(fullText => {
console.log('完整文本:', fullText);
});
3. 使用Response流处理方法
除了直接使用body.getReader()
,Response 对象还提供了几种便捷的流处理方法:
3.1. 文本流处理
vbnet
const response = await fetch('https://api.example.com/stream');
// 使用response.text()获取完整文本
const text = await response.text();
console.log(text);
// 注意:一旦使用了response.text(),流就会被消费
// 不能再次读取body.getReader()
3.2. JSON 流处理
对于 JSON 流,可以使用自定义处理逻辑:
ini
async function streamJSON() {
const response = await fetch('https://api.example.com/json-stream');
const reader = response.body.getReader();
const decoder = new TextDecoder();
let buffer = '';
while (true) {
const { done, value } = await reader.read();
if (done) break;
buffer += decoder.decode(value, { stream: true });
// 尝试解析完整的JSON对象
try {
// 查找JSON对象的边界
const boundary = buffer.indexOf('}\n');
if (boundary !== -1) {
const jsonStr = buffer.substring(0, boundary + 1);
buffer = buffer.substring(boundary + 2);
const jsonObj = JSON.parse(jsonStr);
console.log('解析到JSON对象:', jsonObj);
// 在这里处理JSON对象
}
} catch (e) {
// JSON尚未完整,继续等待更多数据
}
}
}
4. 流式处理二进制数据
Fetch API 对二进制数据的流式处理同样强大:
ini
async function streamBinaryData() {
const response = await fetch('https://example.com/large-file.zip');
const contentLength = response.headers.get('Content-Length');
const totalLength = contentLength ? parseInt(contentLength, 10) : 0;
const reader = response.body.getReader();
let receivedLength = 0;
const chunks = [];
while (true) {
const { done, value } = await reader.read();
if (done) {
console.log('流读取完成');
break;
}
chunks.push(value);
receivedLength += value.length;
const progress = totalLength
? Math.round((receivedLength / totalLength) * 100)
: '未知';
console.log(`已接收: ${receivedLength} 字节 (${progress}%)`);
}
// 合并数据块
const allChunks = new Uint8Array(receivedLength);
let position = 0;
for (const chunk of chunks) {
allChunks.set(chunk, position);
position += chunk.length;
}
return allChunks;
}
// 使用示例 - 下载并处理二进制文件
streamBinaryData().then(data => {
// 创建Blob对象
const blob = new Blob([data]);
// 创建下载链接
const url = URL.createObjectURL(blob);
const a = document.createElement('a');
a.href = url;
a.download = 'downloaded-file.zip';
a.click();
URL.revokeObjectURL(url);
});
5. 使用TransformStream处理流数据
Web Streams API 提供了TransformStream
,可用于在流式传输过程中转换数据:
ini
async function fetchAndTransform() {
const response = await fetch('https://api.example.com/data');
// 创建一个TransformStream,将小写文本转换为大写
const toUpperCaseStream = new TransformStream({
transform(chunk, controller) {
const text = new TextDecoder().decode(chunk);
const upperText = text.toUpperCase();
const upperChunk = new TextEncoder().encode(upperText);
controller.enqueue(upperChunk);
}
});
// 将响应通过转换流
const transformedStream = response.body.pipeThrough(toUpperCaseStream);
// 读取转换后的流
const reader = transformedStream.getReader();
const decoder = new TextDecoder();
let result = '';
while (true) {
const { done, value } = await reader.read();
if (done) break;
result += decoder.decode(value, { stream: true });
}
return result;
}
6. 服务器发送事件(SSE)与Fetch
服务器发送事件(SSE)是一种常见的流式数据应用,可以与 Fetch 结合使用:
ini
async function setupSSE() {
const response = await fetch('https://api.example.com/events');
const reader = response.body.getReader();
const decoder = new TextDecoder();
let buffer = '';
while (true) {
const { done, value } = await reader.read();
if (done) break;
buffer += decoder.decode(value, { stream: true });
// 处理SSE格式
const lines = buffer.split('\n\n');
buffer = lines.pop() || '';
for (const eventText of lines) {
if (!eventText.trim()) continue;
const event = {};
for (const line of eventText.split('\n')) {
if (line.startsWith('data: ')) {
event.data = line.substring(6);
} else if (line.startsWith('event: ')) {
event.type = line.substring(7);
} else if (line.startsWith('id: ')) {
event.id = line.substring(4);
}
}
console.log('收到事件:', event);
// 处理事件数据
}
}
}
7. 取消流式请求
使用 AbortController 可以取消正在进行的流式数据处理:
javascript
async function cancelableStream() {
const controller = new AbortController();
const signal = controller.signal;
// 5秒后取消请求
setTimeout(() => {
console.log('取消请求');
controller.abort();
}, 5000);
try {
const response = await fetch('https://api.example.com/longstream', { signal });
const reader = response.body.getReader();
while (true) {
const { done, value } = await reader.read();
if (done) break;
console.log('收到数据:', value);
}
} catch (error) {
if (error.name === 'AbortError') {
console.log('流传输已被用户取消');
} else {
console.error('流传输出错:', error);
}
}
}
8. 实际应用场景
8.1. 大文件上传进度
javascript
async function uploadWithProgress(file) {
const controller = new AbortController();
const progressCallback = (percent) => {
console.log(`上传进度: ${percent}%`);
// 更新UI进度条
};
try {
// 创建可读流
const fileStream = file.stream();
const reader = fileStream.getReader();
const contentLength = file.size;
let uploadedLength = 0;
// 创建转换流来跟踪进度
const progressStream = new TransformStream({
transform(chunk, controller) {
uploadedLength += chunk.length;
const percent = Math.round((uploadedLength / contentLength) * 100);
progressCallback(percent);
controller.enqueue(chunk);
}
});
// 将文件通过进度流
const readableStream = new ReadableStream({
async start(controller) {
while (true) {
const { done, value } = await reader.read();
if (done) {
controller.close();
break;
}
controller.enqueue(value);
}
}
}).pipeThrough(progressStream);
// 发送请求
const response = await fetch('https://api.example.com/upload', {
method: 'POST',
body: readableStream,
headers: {
'Content-Type': 'application/octet-stream',
'Content-Length': contentLength.toString()
},
signal: controller.signal
});
if (!response.ok) {
throw new Error(`上传失败:${response.status} ${response.statusText}`);
}
return await response.json();
} catch (error) {
console.error('上传出错:', error);
throw error;
}
}
8.2. 实时数据可视化
javascript
async function visualizeRealTimeData() {
try {
const response = await fetch('https://api.example.com/realtime-data');
const reader = response.body.getReader();
const decoder = new TextDecoder();
while (true) {
const { done, value } = await reader.read();
if (done) {
console.log('数据流结束');
break;
}
const chunk = decoder.decode(value, { stream: true });
const dataPoints = chunk.trim().split('\n').map(line => JSON.parse(line));
for (const point of dataPoints) {
// 更新数据可视化
updateChart(point);
}
}
} catch (error) {
console.error('数据流处理错误:', error);
}
}
function updateChart(dataPoint) {
// 将新数据添加到图表
console.log('添加数据点到图表:', dataPoint);
// 实际图表更新代码...
}
8.3. 常见陷阱与注意事项
- 流只能消费一次:一旦流被读取,就不能再次读取
- 内存管理:虽然流式处理可以减少内存使用,但仍需注意大量数据累积
- 错误处理:流处理过程中的错误需要特别注意捕获
- 后端支持:要确保服务器端正确实现了流式传输
javascript
// 错误示例:尝试多次消费流
async function incorrectUsage() {
const response = await fetch('https://api.example.com/data');
// 第一次消费流
const text = await response.text();
console.log('文本:', text);
// 错误:流已被消费,无法再次读取
try {
const reader = response.body.getReader();
const { value } = await reader.read();
console.log('这行代码永远不会执行');
} catch (error) {
console.error('错误:', error); // 将抛出错误
}
}