JavaScript的 fetch() 方法 笔记250810
JavaScript的 fetch() 方法 笔记250810
JavaScript 的 fetch()
方法是现代浏览器提供的用于发起网络请求的 API。它基于 Promise 设计,相比传统的 XMLHttpRequest
更简洁强大。以下是核心知识点和示例:
基础语法
fetch(url [, options]).then(response => /* 处理响应 */).catch(error => /* 处理错误 */);
url
:请求的目标地址(必需)options
:配置对象(可选),包含:method
:请求方法(GET
,POST
,PUT
等),默认为GET
headers
:请求头(如{ 'Content-Type': 'application/json' }
)body
:请求体数据(POST/PUT
时使用,需字符串化)credentials
:是否发送 cookies(include
、same-origin
或omit
)mode
:跨域模式(cors
、no-cors
、same-origin
)
关键特性
-
返回 Promise 对象
- 网络错误(如无法连接)会触发
catch
。 - HTTP 错误状态(如 404、500)不会触发
catch
,需手动检查response.ok
。
- 网络错误(如无法连接)会触发
-
需解析响应体
响应对象Response
提供多种解析方法:response.json()
→ 解析为 JSON 对象response.text()
→ 解析为文本response.blob()
→ 解析为二进制 Blob
-
默认不携带 Cookies
需显式设置credentials: 'include'
。
使用示例
1. GET 请求(获取 JSON 数据)
fetch('https://api.example.com/data').then(response => {if (!response.ok) throw new Error('网络响应异常');return response.json(); // 解析 JSON}).then(data => console.log(data)).catch(error => console.error('请求失败:', error));
2. POST 请求(提交 JSON)
fetch('https://api.example.com/users', {method: 'POST',headers: {'Content-Type': 'application/json','Authorization': 'Bearer YOUR_TOKEN'},body: JSON.stringify({ name: 'Alice', age: 30 }), // 对象转 JSON 字符串credentials: 'include' // 携带 Cookies
}).then(response => response.json()).then(data => console.log('创建成功:', data));
3. 错误处理(统一捕获)
async function fetchData() {try {const response = await fetch('https://invalid-url');if (!response.ok) throw new Error(`HTTP 错误! 状态码: ${response.status}`);const data = await response.json();return data;} catch (error) {console.error('请求出错:', error);}
}
4. 取消请求(使用 AbortController )
const controller = new AbortController();
const { signal } = controller;// 设置超时取消(5秒)
setTimeout(() => controller.abort(), 5000);fetch('https://slow-api.com/data', { signal }).then(response => response.json()).catch(err => {if (err.name === 'AbortError') {console.log('请求被取消!');}});
常见问题
-
跨域请求(CORS)
服务端需设置Access-Control-Allow-Origin
等响应头,否则请求会被浏览器拦截。 -
HTTP 错误状态处理
务必检查response.ok
或response.status
,因为fetch
只在网络故障时拒绝 Promise。 -
请求体格式
body
必须是字符串(如JSON.stringify()
)、FormData
或Blob
对象。
兼容性
- 所有现代浏览器(Chrome 42+、Firefox 39+、Safari 10.1+)均支持。
- 旧浏览器(如 IE)需使用 polyfill(如 whatwg-fetch)。
通过 fetch()
,你可以更优雅地处理网络请求,结合 async/await
语法能大幅提升代码可读性。
fetch() 函数详解
JavaScript 的 fetch() 函数详解
fetch()
是现代 JavaScript 中用于发起网络请求的核心 API,基于 Promise 设计,提供了比传统 XMLHttpRequest
更简洁强大的功能。下面从多个维度深入解析:
一、基础架构
fetch(resource [, init]).then(response => { /* 处理响应 */ }).catch(error => { /* 处理错误 */ });
参数 | 类型 | 说明 |
---|---|---|
resource | String/Request | 请求 URL 或 Request 对象实例 |
init | Object | 可选配置对象(见下文) |
二、配置对象(init)详解
{// 请求方法(默认:'GET')method: 'POST', // 请求头headers: {'Content-Type': 'application/json','Authorization': 'Bearer token123'},// 请求体(GET/HEAD 请求不能包含)body: JSON.stringify({ key: 'value' }),// 凭证控制credentials: 'include', // 可选值: 'omit'(默认)、'same-origin'、'include'// 请求模式mode: 'cors', // 可选值: 'cors'(默认)、'no-cors'、'same-origin'// 缓存策略cache: 'no-cache', // 可选值: 'default'、'no-store'、'reload' 等// 重定向策略redirect: 'follow', // 可选值: 'follow'(默认)、'error'、'manual'// 引用策略referrerPolicy: 'no-referrer',// 完整性校验integrity: 'sha256-BpfBw7ivV8q2jLiT13...',// 信号控制(用于取消请求)signal: abortController.signal
}
三、响应处理(Response 对象)
fetch()
返回的 Promise 解析为 Response
对象,包含以下重要属性和方法:
属性:
response.ok
:布尔值,HTTP 状态码 200-299 时为 trueresponse.status
:HTTP 状态码(如 200、404)response.statusText
:状态文本(如 “OK”)response.headers
:响应头对象response.url
:最终请求 URL(考虑重定向后)
解析方法:
response.json()
→ 解析为 JSON 对象response.text()
→ 解析为字符串response.blob()
→ 解析为 Blob 对象response.arrayBuffer()
→ 解析为 ArrayBufferresponse.formData()
→ 解析为 FormData 对象
注意:每个解析方法只能使用一次,如需多次使用需先克隆响应:
const cloned = response.clone()
四、完整请求生命周期示例
1. GET 请求(带错误处理)
fetch('https://api.example.com/data').then(response => {// 检查网络响应状态if (!response.ok) {throw new Error(`HTTP error! Status: ${response.status}`);}// 检查内容类型const contentType = response.headers.get('content-type');if (!contentType.includes('application/json')) {throw new TypeError("非JSON响应");}return response.json();}).then(data => {console.log('获取数据:', data);}).catch(error => {console.error('请求失败:', error);});
2. POST 请求(提交表单数据)
const formData = new FormData();
formData.append('username', 'john');
formData.append('avatar', fileInput.files[0]);fetch('/api/user', {method: 'POST',body: formData, // 自动设置 multipart/form-datacredentials: 'include'
});
3. 带超时的请求(AbortController)
const controller = new AbortController();
const timeoutId = setTimeout(() => controller.abort(), 5000);fetch('https://slow-api.com/data', {signal: controller.signal
}).then(response => response.json()).then(data => {clearTimeout(timeoutId);console.log(data);}).catch(err => {if (err.name === 'AbortError') {console.log('请求超时取消');} else {console.error('其他错误:', err);}});
五、高级应用场景
1. 并行请求(Promise.all)
const urls = ['/api/user', '/api/posts', '/api/notifications'];Promise.all(urls.map(url => fetch(url).then(res => res.json())
)).then(([user, posts, notifications]) => {console.log('全部数据加载完成');});
2. 流式处理(大文件处理)
fetch('https://example.com/large-video.mp4').then(response => {const reader = response.body.getReader();const stream = new ReadableStream({start(controller) {function push() {reader.read().then(({ done, value }) => {if (done) {controller.close();return;}controller.enqueue(value);push();});}push();}});return new Response(stream);}).then(response => response.blob()).then(blob => {videoElement.src = URL.createObjectURL(blob);});
3. 自定义请求对象
const myRequest = new Request('/api/data', {method: 'PUT',headers: new Headers({'X-Custom-Header': 'value'}),body: JSON.stringify({ update: 'new value' })
});fetch(myRequest).then(/* ... */);
六、关键注意事项
-
CORS 限制
跨域请求需要服务器设置正确的响应头(如Access-Control-Allow-Origin
) -
Cookie 策略
默认不发送/接收 cookies,需显式设置credentials: 'include'
-
错误处理差异
- 网络故障:触发
catch
或 Promise 拒绝 - HTTP 错误(4xx/5xx):需手动检查
response.ok
- 网络故障:触发
-
数据格式处理
- JSON 数据需使用
JSON.stringify()
转换 - FormData 会自动设置正确的 Content-Type
- JSON 数据需使用
-
性能优化
- 使用
AbortController
实现请求超时和取消 - 大文件处理优先使用流式 API
- 使用
七、浏览器兼容性
浏览器 | 支持版本 | 备注 |
---|---|---|
Chrome | 42+ | 完整支持 |
Firefox | 39+ | 完整支持 |
Safari | 10.1+ | 部分高级特性需更新版本 |
Edge | 14+ | 完整支持 |
Internet Explorer | 不支持 | 需使用 polyfill |
Polyfill 方案:
<!-- 通过 npm 安装 -->
<script src="https://cdn.jsdelivr.net/npm/whatwg-fetch@3.6.2/dist/fetch.umd.min.js"></script>
八、最佳实践总结
- 始终检查
response.ok
或response.status
- 使用
async/await
提升代码可读性 - 为敏感请求设置超时限制(AbortController)
- 根据响应内容类型选择正确的解析方法
- 跨域请求确保服务器配置 CORS 头部
- 需要 cookies 认证时设置
credentials: 'include'
通过掌握这些核心概念,您可以高效安全地使用 fetch()
处理各种网络请求场景。
JavaScript Fetch API 全面指南
Fetch API 是现代 JavaScript 中用于网络请求的核心技术,它基于 Promise 设计,提供了比传统 XMLHttpRequest 更强大、更灵活的解决方案。本指南将深入探讨 Fetch API 的各个方面。
一、Fetch API 核心概念
1. 基本架构
fetch(resource [, init]).then(response => { /* 处理响应 */ }).catch(error => { /* 处理错误 */ });
- resource:请求资源(URL 字符串或 Request 对象)
- init:配置对象(可选)
2. 与传统 XHR 的对比
特性 | Fetch API | XMLHttpRequest |
---|---|---|
语法 | Promise-based | 回调函数 |
请求取消 | AbortController | xhr.abort() |
流式处理 | 原生支持 | 有限支持 |
CORS 处理 | 更简洁 | 较复杂 |
请求体/响应体类型 | 丰富(Blob、FormData等) | 有限 |
二、请求配置详解
1. 完整配置选项
{method: 'POST', // HTTP 方法headers: { // 请求头'Content-Type': 'application/json','Authorization': 'Bearer token'},body: JSON.stringify(data), // 请求体mode: 'cors', // 跨域模式cache: 'no-cache', // 缓存策略credentials: 'include', // 凭证控制redirect: 'follow', // 重定向策略referrerPolicy: 'no-referrer', // 来源策略integrity: 'sha256-...', // 子资源完整性signal: abortSignal // 中止信号
}
2. 请求体支持类型
- JSON 数据:
JSON.stringify({ key: 'value' })
- FormData:
new FormData(formElement)
- URLSearchParams:
new URLSearchParams({ key: 'value' })
- Blob/BufferSource:二进制数据
- 字符串:普通文本
三、响应处理深度解析
1. Response 对象结构
{ok: true, // 状态码 200-299 时为 truestatus: 200, // HTTP 状态码statusText: 'OK', // 状态文本headers: Headers {}, // 响应头对象url: 'https://...', // 最终 URL(含重定向)type: 'cors', // 响应类型redirected: false, // 是否重定向body: ReadableStream, // 可读流bodyUsed: false // 是否已读取
}
2. 响应数据解析方法
// JSON 数据
const data = await response.json();// 文本内容
const text = await response.text();// Blob 对象(如图片)
const blob = await response.blob();// ArrayBuffer(二进制数据)
const buffer = await response.arrayBuffer();// FormData(表单数据)
const formData = await response.formData();
重要:每个解析方法只能调用一次,如需多次使用需先克隆响应:
const clone1 = response.clone(); const clone2 = response.clone(); const data1 = await clone1.json(); const data2 = await clone2.text();
四、高级应用场景
1. 请求取消(AbortController)
const controller = new AbortController();
const timeoutId = setTimeout(() => controller.abort(), 5000);try {const response = await fetch(url, {signal: controller.signal});clearTimeout(timeoutId);// 处理响应...
} catch (err) {if (err.name === 'AbortError') {console.log('请求被取消');} else {console.error('请求错误:', err);}
}
2. 流式数据处理
const response = await fetch('https://example.com/large-file');
const reader = response.body.getReader();while (true) {const { done, value } = await reader.read();if (done) break;console.log('接收到数据块:', value);// 处理数据块(如实时渲染)
}
3. 上传进度监控
const fileInput = document.querySelector('input[type="file"]');
const file = fileInput.files[0];const xhr = new XMLHttpRequest();
xhr.upload.onprogress = (event) => {const percent = Math.round((event.loaded / event.total) * 100);console.log(`上传进度: ${percent}%`);
};// 使用 Fetch + Progress (实验性)
const response = await fetch(url, {method: 'POST',body: file,duplex: 'half' // 必需
});// 使用 ReadableStream 模拟进度
const progressStream = new TransformStream({transform(chunk, controller) {loaded += chunk.byteLength;updateProgress(loaded, total);controller.enqueue(chunk);}
});
4. 自定义请求拦截
// 全局请求拦截
const originalFetch = window.fetch;window.fetch = async (resource, init) => {// 请求前处理(如添加认证头)init.headers = {...init.headers,'X-Auth-Token': getToken()};const start = Date.now();const response = await originalFetch(resource, init);const duration = Date.now() - start;// 响应后处理(如日志记录)logRequest(resource, response.status, duration);return response;
};
五、错误处理最佳实践
1. 综合错误处理方案
async function safeFetch(url, options) {try {const response = await fetch(url, options);// 处理 HTTP 错误状态if (!response.ok) {const error = new Error(`HTTP 错误! 状态码: ${response.status}`);error.status = response.status;throw error;}// 根据内容类型解析const contentType = response.headers.get('content-type');if (contentType.includes('application/json')) {return await response.json();} else if (contentType.includes('text/')) {return await response.text();} else {return await response.blob();}} catch (error) {// 分类处理不同错误类型if (error.name === 'AbortError') {console.warn('请求被取消');} else if (error.name === 'TypeError') {console.error('网络错误或 CORS 问题');} else {console.error('请求失败:', error);}// 返回统一错误格式return {success: false,error: error.message,status: error.status || 0};}
}
六、性能优化技巧
-
请求合并:
// 并行请求 const [user, posts] = await Promise.all([fetch('/api/user').then(r => r.json()),fetch('/api/posts').then(r => r.json()) ]);
-
缓存策略:
fetch(url, {cache: 'force-cache' // 优先使用缓存 });// 或者 fetch(url, {cache: 'reload' // 绕过缓存 });
-
数据压缩:
fetch(url, {headers: {'Accept-Encoding': 'gzip, deflate, br'} });
-
HTTP/2 服务端推送:
// 服务器端配置 Link: </styles.css>; rel=preload; as=style
七、安全实践
-
CSRF 防护:
fetch(url, {headers: {'X-CSRF-Token': getCSRFToken()} });
-
内容安全策略(CSP):
<meta http-equiv="Content-Security-Policy" content="default-src 'self'; script-src 'self' 'unsafe-inline'">
-
子资源完整性(SRI):
fetch('https://cdn.example.com/library.js', {integrity: 'sha384-...' });
-
CORS 安全配置:
// 服务器端响应头 Access-Control-Allow-Origin: https://yourdomain.com Access-Control-Allow-Credentials: true
八、浏览器兼容性与 Polyfill
浏览器 | 支持版本 | 注意事项 |
---|---|---|
Chrome | 42+ | 完整支持 |
Firefox | 39+ | 完整支持 |
Safari | 10.1+ | 部分高级特性需更新版本 |
Edge | 14+ | 完整支持 |
IE | 不支持 | 需使用 polyfill |
Polyfill 方案:
<!-- 使用 whatwg-fetch polyfill -->
<script src="https://cdn.jsdelivr.net/npm/whatwg-fetch@3.6.2/dist/fetch.umd.min.js"></script><!-- 或通过 npm 安装 -->
npm install whatwg-fetch
// 在应用入口引入
import 'whatwg-fetch';
九、实际应用示例
1. 文件分片上传
async function uploadFile(file) {const CHUNK_SIZE = 5 * 1024 * 1024; // 5MBconst totalChunks = Math.ceil(file.size / CHUNK_SIZE);for (let i = 0; i < totalChunks; i++) {const chunk = file.slice(i * CHUNK_SIZE, (i + 1) * CHUNK_SIZE);const formData = new FormData();formData.append('file', chunk);formData.append('chunkIndex', i);formData.append('totalChunks', totalChunks);formData.append('fileName', file.name);await fetch('/api/upload', {method: 'POST',body: formData});console.log(`上传分片 ${i + 1}/${totalChunks}`);}// 通知服务器合并文件await fetch('/api/merge', {method: 'POST',headers: { 'Content-Type': 'application/json' },body: JSON.stringify({ fileName: file.name })});
}
2. 带重试机制的请求
async function fetchWithRetry(url, options, maxRetries = 3) {for (let i = 0; i < maxRetries; i++) {try {const response = await fetch(url, options);if (response.ok) return response;// 服务器错误时重试if (response.status >= 500) {throw new Error(`服务器错误: ${response.status}`);}return response;} catch (error) {if (i === maxRetries - 1) throw error;// 指数退避重试const delay = Math.pow(2, i) * 1000 + Math.random() * 1000;await new Promise(resolve => setTimeout(resolve, delay));}}
}
十、未来发展趋势
-
Fetch 元数据:
// 实验性功能 fetch(url, {priority: 'high' // 请求优先级 });
-
请求诊断 API:
const resourceTiming = performance.getEntriesByName(url)[0]; console.log('DNS 时间:', resourceTiming.domainLookupEnd - resourceTiming.domainLookupStart);
-
WebTransport:
// 下一代传输协议(基于 HTTP/3) const transport = new WebTransport('https://example.com'); const stream = await transport.createBidirectionalStream();
-
后台同步 API:
// 服务工作者中使用 self.registration.sync.register('sync-data');
总结
Fetch API 是现代 Web 开发的基石,提供了强大而灵活的网络请求能力。关键要点:
- 始终检查
response.ok
处理 HTTP 错误 - 使用
AbortController
实现请求取消和超时 - 根据内容类型选择合适的解析方法
- 重要请求实现重试和回退机制
- 敏感请求添加 CSRF 保护
- 大文件处理使用流式 API
- 兼容旧浏览器使用 polyfill
随着 Web 平台的发展,Fetch API 将继续演进,为开发者提供更强大的网络能力。掌握这些技术将使您能够构建更健壮、更高效的 Web 应用。
fetch 的配置项
Fetch API 配置项全面指南
Fetch API 的配置项是控制 HTTP 请求行为的核心参数,通过 fetch(url, options)
的第二个参数传递。以下是所有可用配置项的详细说明和示例:
一、基础配置项
配置项 | 类型 | 默认值 | 说明 |
---|---|---|---|
method | String | ‘GET’ | HTTP 方法 (GET, POST, PUT, DELETE, PATCH, HEAD, OPTIONS) |
headers | Object | {} | 请求头对象 |
body | Various | null | 请求体数据(Blob, BufferSource, FormData, URLSearchParams, String) |
mode | String | ‘cors’ | 请求模式:‘cors’, ‘no-cors’, ‘same-origin’, ‘navigate’ |
credentials | String | ‘same-origin’ | 凭证控制:‘omit’, ‘same-origin’, ‘include’ |
cache | String | ‘default’ | 缓存策略:‘default’, ‘no-store’, ‘reload’, ‘no-cache’, ‘force-cache’, ‘only-if-cached’ |
redirect | String | ‘follow’ | 重定向策略:‘follow’, ‘error’, ‘manual’ |
referrer | String | ‘about:client’ | 来源页面 URL |
referrerPolicy | String | ‘no-referrer-when-downgrade’ | 来源策略(控制 Referer 头) |
integrity | String | ‘’ | 子资源完整性校验(如 ‘sha256-BpfBw7ivV8q2jLiT13…’) |
signal | AbortSignal | null | 用于取消请求的信号对象 |
priority | String | ‘auto’ | 请求优先级:‘high’, ‘low’, ‘auto’(实验性功能) |
duplex | String | - | 双工模式:‘half’(实验性,用于支持进度监控) |
二、配置项详解与示例
1. 基础请求配置
// POST 请求示例
fetch('/api/data', {method: 'POST',headers: {'Content-Type': 'application/json','Authorization': 'Bearer token123'},body: JSON.stringify({ key: 'value' })
});
2. 跨域与凭证控制
// 跨域请求带凭证
fetch('https://cross-origin.com/api', {mode: 'cors', // 必需跨域模式credentials: 'include', // 包含 cookiesheaders: {'X-Requested-With': 'XMLHttpRequest'}
});
3. 缓存控制策略
// 强制缓存验证
fetch('/static/data.json', {cache: 'no-cache' // 使用缓存但验证新鲜度
});// 完全绕过缓存
fetch('/latest-data', {cache: 'reload' // 忽略缓存,直接从服务器获取
});// 仅使用缓存
fetch('/cached-data', {cache: 'only-if-cached' // 仅从缓存读取,不发送请求
});
4. 重定向处理
// 禁止重定向
fetch('/redirect-endpoint', {redirect: 'error' // 遇到重定向时抛出错误
});// 手动处理重定向
fetch('/redirect', {redirect: 'manual'
}).then(response => {if (response.type === 'opaqueredirect') {// 手动处理重定向window.location = response.url;}
});
5. 请求完整性校验
// 确保资源未被篡改
fetch('/critical-script.js', {integrity: 'sha384-oqVuAfXRKap7fdgcCY5uykM6+R9GqQ8K/uxy9rx7HNQlGYl1kPzQho1wx4JwY8wC'
});
6. 取消请求(AbortController)
const controller = new AbortController();
const timeoutId = setTimeout(() => controller.abort(), 5000);fetch('/slow-api', {signal: controller.signal
}).then(response => {clearTimeout(timeoutId);return response.json();}).catch(err => {if (err.name === 'AbortError') {console.log('请求超时取消');}});
7. 请求优先级(实验性)
// 高优先级请求(如图片/关键数据)
fetch('/hero-image.jpg', {priority: 'high'
});// 低优先级请求(如日志、分析)
fetch('/analytics', {priority: 'low'
});
三、body 数据类型处理
1. 不同数据类型的 body 配置
数据类型 | Content-Type 头 | 示例 |
---|---|---|
JSON 对象 | application/json | body: JSON.stringify({key: 'value'}) |
FormData | multipart/form-data | body: new FormData(formElement) |
URLSearchParams | application/x-www-form-urlencoded | body: new URLSearchParams({key: 'value'}) |
Blob/File | 自动设置 | body: new Blob([JSON.stringify(data)]) |
ArrayBuffer | 根据内容设置 | body: new Uint8Array([1,2,3]).buffer |
字符串 | text/plain | body: 'plain text' |
2. 完整示例
// FormData 上传文件
const formData = new FormData();
formData.append('file', fileInput.files[0]);
formData.append('comment', '文件说明');fetch('/upload', {method: 'POST',body: formData // 自动设置 multipart/form-data
});// URLSearchParams 提交表单
const params = new URLSearchParams();
params.append('search', 'JavaScript');
params.append('page', '1');fetch('/search', {method: 'POST',body: params // 自动设置 application/x-www-form-urlencoded
});// Blob 上传
const blob = new Blob([JSON.stringify({ data: 'value' })], {type: 'application/json'
});fetch('/submit', {method: 'POST',body: blob
});
四、headers 配置详解
1. 设置请求头
// 使用 Headers 对象
const headers = new Headers();
headers.append('Content-Type', 'application/json');
headers.append('X-Custom-Header', 'value');// 或使用普通对象
fetch('/api', {headers: {'Content-Type': 'application/json','Authorization': `Bearer ${token}`,'X-Request-ID': uuidv4()}
});
2. 禁止的请求头
以下请求头由浏览器控制,无法通过 JavaScript 设置:
Accept-Charset
Accept-Encoding
Access-Control-Request-Headers
Access-Control-Request-Method
Connection
Content-Length
Cookie
Cookie2
Date
DNT
Expect
Host
Keep-Alive
Origin
Referer
TE
Trailer
Transfer-Encoding
Upgrade
Via
五、高级配置场景
1. 流式上传(带进度)
// 创建带进度监控的可读流
function createProgressStream(stream, onProgress) {let loaded = 0;return new ReadableStream({start(controller) {const reader = stream.getReader();async function push() {const { done, value } = await reader.read();if (done) {controller.close();return;}loaded += value.length;onProgress(loaded);controller.enqueue(value);push();}push();}});
}const fileStream = file.stream();
const progressStream = createProgressStream(fileStream, loaded => {console.log(`已上传: ${loaded} 字节`);
});fetch('/upload', {method: 'POST',headers: { 'Content-Type': file.type },body: progressStream,duplex: 'half' // 必需参数
});
2. 服务端事件 (SSE) 替代方案
// 使用 Fetch 实现类 SSE 功能
async function streamEvents(url) {const response = await fetch(url, {headers: { 'Accept': 'text/event-stream' }});const reader = response.body.getReader();const decoder = new TextDecoder();while (true) {const { done, value } = await reader.read();if (done) break;const chunk = decoder.decode(value, { stream: true });chunk.split('\n\n').forEach(event => {if (event.trim()) {const [id, type, data] = parseEvent(event);handleEvent({ id, type, data });}});}
}
3. HTTP/2 服务器推送
// 利用服务器推送(需要服务端支持)
fetch('/main-resource', {headers: { 'Accept': 'multipart/mixed' }
}).then(response => {// 处理主资源和推送资源const reader = response.body.getReader();// ...解析多部分响应...
});
六、配置项最佳实践
-
始终设置超时:
function fetchWithTimeout(url, options, timeout = 8000) {const controller = new AbortController();setTimeout(() => controller.abort(), timeout);return fetch(url, { ...options, signal: controller.signal }); }
-
JSON 请求封装:
async function jsonFetch(url, method = 'GET', data = null) {const options = {method,headers: { 'Content-Type': 'application/json' },credentials: 'include'};if (data) options.body = JSON.stringify(data);const response = await fetch(url, options);if (!response.ok) throw new Error(`HTTP ${response.status}`);return response.json(); }
-
安全请求配置:
fetch('/sensitive', {method: 'POST',credentials: 'include',integrity: 'sha256-...', // 完整性校验headers: {'Content-Type': 'application/json','X-CSRF-Token': getCSRFToken(), // CSRF 防护'Strict-Transport-Security': 'max-age=31536000' // HSTS} });
七、浏览器兼容性注意事项
-
实验性功能支持:
priority
:仅 Chrome 65+ 支持duplex
:仅 Chrome 105+ 支持integrity
:所有现代浏览器支持
-
Polyfill 方案:
<!-- 基础 Fetch polyfill --> <script src="https://cdn.jsdelivr.net/npm/whatwg-fetch@3.6.2/dist/fetch.umd.min.js"></script><!-- AbortController polyfill --> <script src="https://cdn.jsdelivr.net/npm/abortcontroller-polyfill@1.7.3/dist/abortcontroller-polyfill-only.min.js"></script>
-
Node.js 支持:
// Node.js 18+ 实验性支持 const fetch = (...args) => import('node-fetch').then(({default: fetch}) => fetch(...args));// 或使用 node-fetch 库 const fetch = require('node-fetch');
八、常见问题解决方案
-
CORS 问题:
fetch('https://cross-origin.com/api', {mode: 'cors',headers: {// 服务器需要配置 Access-Control-Allow-Origin} });
-
Cookie 未发送:
fetch('/auth', {credentials: 'include' // 必需 });
-
多次读取响应体:
fetch('/data').then(response => {const clone = response.clone(); // 克隆响应return Promise.all([response.json(),clone.text()]);});
-
上传进度监控:
// 使用 XMLHttpRequest 替代或实验性 Streams API
掌握 Fetch API 的配置项是优化网络请求的关键,合理组合这些选项可以实现从简单请求到复杂场景的全方位控制。根据目标浏览器环境选择合适的配置,并始终考虑安全性和错误处理,将大大提升应用的网络请求质量。
fetch 的 Response
Response 对象详解
在 JavaScript Fetch API 中,Response
对象代表了对 HTTP 请求的响应。它包含了服务器返回的所有信息,并提供了多种方法来处理响应数据。以下是 Response 对象的全面解析:
一、Response 对象属性详解
属性 | 类型 | 说明 |
---|---|---|
ok | Boolean | 状态码在 200-299 范围内时为 true |
status | Number | HTTP 状态码(如 200, 404, 500 等) |
statusText | String | 状态文本(如 “OK”, “Not Found”) |
url | String | 最终响应 URL(考虑重定向后的地址) |
headers | Headers 对象 | 响应头信息 |
type | String | 响应类型(basic , cors , error , opaque , opaqueredirect ) |
redirected | Boolean | 是否经过重定向 |
body | ReadableStream | 可读流形式的响应体 |
bodyUsed | Boolean | 是否已读取过响应体 |
示例:访问属性
fetch('https://api.example.com/data').then(response => {console.log('状态码:', response.status);console.log('状态文本:', response.statusText);console.log('内容类型:', response.headers.get('content-type'));console.log('响应URL:', response.url);console.log('是否成功:', response.ok);});
二、响应体处理方法
Response 对象提供多种方法解析响应体,每个方法只能调用一次:
1. response.json()
将响应体解析为 JSON 对象
fetch('/api/data').then(response => response.json()).then(data => console.log(data));
2. response.text()
获取响应体文本内容
fetch('/text-file.txt').then(response => response.text()).then(text => console.log(text));
3. response.blob()
获取二进制 Blob 对象(适合文件下载)
fetch('/image.png').then(response => response.blob()).then(blob => {const img = new Image();img.src = URL.createObjectURL(blob);document.body.appendChild(img);});
4. response.arrayBuffer()
获取原始二进制 ArrayBuffer(适合音频/视频处理)
fetch('/audio.mp3').then(response => response.arrayBuffer()).then(buffer => {const audioCtx = new AudioContext();audioCtx.decodeAudioData(buffer, audio => {// 处理音频数据});});
5. response.formData()
解析表单数据
fetch('/form-submit').then(response => response.formData()).then(formData => {console.log(formData.get('username'));});
三、Headers 对象操作
response.headers
提供对响应头的访问:
// 获取单个头信息
const contentType = response.headers.get('content-type');// 检查头是否存在
if (response.headers.has('X-Custom-Header')) {// ...
}// 遍历所有头信息
response.headers.forEach((value, name) => {console.log(`${name}: ${value}`);
});// 转为普通对象
const headersObj = Object.fromEntries(response.headers.entries());
四、克隆响应对象
当需要多次使用响应体时(如同时获取 JSON 和原始文本),需先克隆响应:
fetch('/api/data').then(response => {// 创建两个克隆const jsonClone = response.clone();const textClone = response.clone();return Promise.all([jsonClone.json(),textClone.text()]);}).then(([data, rawText]) => {console.log('Parsed data:', data);console.log('Raw text:', rawText);});
五、响应类型(Response.type)
类型 | 说明 |
---|---|
basic | 同源响应 |
cors | 有效的跨域响应(包含 CORS 头) |
error | 网络错误(无法完成请求) |
opaque | 跨域请求但未返回 CORS 头(无法读取状态/头信息) |
opaqueredirect | 重定向模式设为 “manual” 时的重定向响应 |
检测响应类型:
fetch('https://cross-origin.com/data').then(response => {if (response.type === 'cors') {console.log('有效的跨域响应');} else if (response.type === 'opaque') {console.log('受限的跨域响应');}});
六、自定义 Response 对象
可手动创建 Response 对象用于测试或 Service Worker:
// 创建简单文本响应
const textResponse = new Response('Hello, world!', {status: 200,headers: new Headers({'Content-Type': 'text/plain'})
});// 创建 JSON 响应
const jsonResponse = new Response(JSON.stringify({ data: 123 }), {status: 200,headers: { 'Content-Type': 'application/json' }
});// 创建错误响应
const errorResponse = Response.error(); // status=0, type=error
七、流式处理响应体
处理大文件时使用流式 API:
fetch('/large-video.mp4').then(response => {const reader = response.body.getReader();const stream = new ReadableStream({start(controller) {function push() {reader.read().then(({ done, value }) => {if (done) {controller.close();return;}controller.enqueue(value);push();});}push();}});return new Response(stream);}).then(response => response.blob()).then(blob => {// 处理大文件});
八、在 Service Worker 中的高级用法
1. 缓存响应
self.addEventListener('fetch', event => {event.respondWith(caches.match(event.request).then(cached => cached || fetch(event.request)));
});
2. 合成响应
self.addEventListener('fetch', event => {if (event.request.url.endsWith('/custom-data')) {const data = { timestamp: Date.now() };const jsonResponse = new Response(JSON.stringify(data), { headers: { 'Content-Type': 'application/json' } });event.respondWith(jsonResponse);}
});
九、注意事项与最佳实践
-
响应体单次读取:
// 错误示例:尝试多次读取 fetch('/data').then(response => {const data = response.json(); // 第一次读取const text = response.text(); // 错误!bodyUsed 已为 true});
-
HTTP 错误处理:
fetch('/api').then(response => {if (!response.ok) {throw new Error(`HTTP ${response.status}`);}return response.json();});
-
内容类型验证:
fetch('/data').then(response => {const contentType = response.headers.get('content-type');if (!contentType.includes('application/json')) {throw new TypeError("Expected JSON response");}return response.json();});
-
超时控制:
const controller = new AbortController(); setTimeout(() => controller.abort(), 5000);fetch('/slow-api', { signal: controller.signal }).then(response => response.json()).catch(err => {if (err.name === 'AbortError') {console.log('Request timed out');}});
十、浏览器兼容性
浏览器 | 支持版本 | 备注 |
---|---|---|
Chrome | 42+ | 完整支持 |
Firefox | 39+ | 完整支持 |
Safari | 10.1+ | 流式处理需更新版本 |
Edge | 14+ | 完整支持 |
Node.js | 18+ | 实验性支持(需启用 flag) |
Polyfill 方案:
npm install node-fetch # Node.js 环境
npm install whatwg-fetch # 浏览器环境
总结
Response 对象是 Fetch API 的核心组件,提供了:
- 访问 HTTP 响应元数据(状态码、头信息等)
- 多种响应体解析方法(JSON、文本、Blob 等)
- 流式处理能力(适合大文件)
- 克隆机制(允许多次使用响应)
- 自定义响应创建能力
掌握 Response 对象的正确使用方式,结合适当的错误处理和流控制,可以构建高效可靠的网络请求处理逻辑,特别是在处理大型数据集或媒体文件时表现出色。
XMLHttpRequest
XMLHttpRequest 全面解析
XMLHttpRequest (XHR) 是浏览器提供的 JavaScript API,用于在客户端和服务器之间传输数据。它是 AJAX (Asynchronous JavaScript and XML) 技术的核心基础,虽然现在有更现代的 Fetch API,但 XHR 仍然广泛使用且功能强大。
一、基础架构与核心概念
1. 创建实例
const xhr = new XMLHttpRequest();
2. 请求生命周期
- 创建对象:
new XMLHttpRequest()
- 配置请求:
.open(method, url, async)
- 设置回调:
.onreadystatechange
- 发送请求:
.send(body)
- 处理响应:检查
readyState
和status
二、核心方法与属性
1. 请求配置方法
// 初始化请求
xhr.open(method, url, async, user, password);// 设置请求头
xhr.setRequestHeader('Content-Type', 'application/json');
xhr.setRequestHeader('Authorization', 'Bearer token');// 设置响应类型
xhr.responseType = 'json'; // 可选: 'text', 'arraybuffer', 'blob', 'document'
2. 状态属性
// 请求状态 (0-4)
xhr.readyState;// HTTP 状态码
xhr.status;// 状态文本
xhr.statusText;// 响应数据
xhr.response;
xhr.responseText; // 当 responseType 为 'text' 时
xhr.responseXML; // 当响应是 XML 时
3. readyState 状态详解
值 | 状态 | 描述 |
---|---|---|
0 | UNSENT | 对象已创建,open() 未调用 |
1 | OPENED | open() 已调用 |
2 | HEADERS_RECEIVED | 收到响应头 |
3 | LOADING | 接收响应体中 |
4 | DONE | 请求完成(成功或失败) |
三、完整请求示例
1. GET 请求
const xhr = new XMLHttpRequest();
xhr.open('GET', 'https://api.example.com/data', true);xhr.onreadystatechange = function() {if (xhr.readyState === 4) {if (xhr.status === 200) {console.log('成功:', JSON.parse(xhr.responseText));} else {console.error('错误:', xhr.statusText);}}
};xhr.onerror = function() {console.error('网络错误');
};xhr.send();
2. POST 请求 (JSON)
const xhr = new XMLHttpRequest();
xhr.open('POST', 'https://api.example.com/submit', true);
xhr.setRequestHeader('Content-Type', 'application/json');xhr.onload = function() {if (xhr.status >= 200 && xhr.status < 300) {console.log('提交成功:', xhr.response);}
};const data = JSON.stringify({ name: 'John', age: 30 });
xhr.send(data);
四、高级功能
1. 超时处理
xhr.timeout = 5000; // 5秒超时
xhr.ontimeout = function() {console.error('请求超时');
};
2. 进度监控
// 上传进度
xhr.upload.onprogress = function(event) {if (event.lengthComputable) {const percent = (event.loaded / event.total) * 100;console.log(`上传: ${percent.toFixed(2)}%`);}
};// 下载进度
xhr.onprogress = function(event) {if (event.lengthComputable) {const percent = (event.loaded / event.total) * 100;console.log(`下载: ${percent.toFixed(2)}%`);}
};
3. 中止请求
const xhr = new XMLHttpRequest();
// ...配置请求...// 中止请求
document.getElementById('cancelBtn').addEventListener('click', () => {xhr.abort();console.log('请求已取消');
});
4. 文件上传
const formData = new FormData();
formData.append('file', fileInput.files[0]);
formData.append('username', 'john_doe');const xhr = new XMLHttpRequest();
xhr.open('POST', '/upload', true);xhr.upload.onprogress = function(e) {// 更新进度条
};xhr.onload = function() {if (xhr.status === 200) {console.log('上传成功');}
};xhr.send(formData);
五、事件处理模型
事件 | 触发时机 |
---|---|
onreadystatechange | readyState 变化时触发 |
onloadstart | 请求开始时触发 |
onprogress | 数据传输过程中定期触发 |
onabort | 请求被中止时触发 |
onerror | 请求失败时触发 |
onload | 请求成功完成时触发 |
ontimeout | 请求超时时触发 |
onloadend | 请求结束时触发(无论成功或失败) |
xhr.addEventListener('load', function() {console.log('请求完成');
});xhr.addEventListener('error', function() {console.error('请求出错');
});xhr.addEventListener('abort', function() {console.warn('请求被取消');
});
六、与 Fetch API 的对比
特性 | XMLHttpRequest | Fetch API |
---|---|---|
基本架构 | 基于事件回调 | 基于 Promise |
请求取消 | 原生支持 (xhr.abort()) | 需使用 AbortController |
进度监控 | 原生支持 (upload/onprogress) | 实验性支持或需自定义实现 |
超时控制 | 原生支持 (timeout 属性) | 需手动实现 |
响应类型 | 多种 (text, arraybuffer, blob 等) | 类似,但通过不同方法访问 |
CORS 处理 | 需要额外处理 | 更简洁的 CORS 支持 |
同步请求 | 支持 (open 第三个参数设为 false) | 不支持 |
浏览器支持 | 所有现代浏览器 + IE7+ | 现代浏览器 (IE 不支持) |
请求体格式 | 支持多种 (FormData, Blob, 字符串) | 类似 |
七、跨域请求处理 (CORS)
1. 简单请求
xhr.open('GET', 'https://cross-origin.com/data', true);
xhr.send();
2. 预检请求 (Preflight)
xhr.open('PUT', 'https://cross-origin.com/update', true);
xhr.setRequestHeader('Content-Type', 'application/json');
xhr.setRequestHeader('X-Custom-Header', 'value');
xhr.send(JSON.stringify({ data: 'test' }));
3. 带凭证的请求
xhr.withCredentials = true; // 发送 cookies
八、最佳实践与错误处理
1. 封装成 Promise
function xhrRequest(method, url, data = null) {return new Promise((resolve, reject) => {const xhr = new XMLHttpRequest();xhr.open(method, url, true);xhr.setRequestHeader('Content-Type', 'application/json');xhr.onload = function() {if (xhr.status >= 200 && xhr.status < 300) {try {resolve(JSON.parse(xhr.responseText));} catch (e) {resolve(xhr.responseText);}} else {reject(new Error(`请求失败: ${xhr.status} ${xhr.statusText}`));}};xhr.onerror = () => reject(new Error('网络错误'));xhr.ontimeout = () => reject(new Error('请求超时'));xhr.send(data ? JSON.stringify(data) : null);});
}// 使用示例
xhrRequest('GET', '/api/data').then(data => console.log(data)).catch(error => console.error(error));
2. 统一错误处理
xhr.onreadystatechange = function() {if (xhr.readyState === 4) {switch (true) {case xhr.status === 0:console.error('请求被取消或网络错误');break;case xhr.status >= 200 && xhr.status < 300:handleSuccess(xhr.response);break;case xhr.status === 401:console.error('未授权,需要登录');break;case xhr.status === 403:console.error('禁止访问');break;case xhr.status === 404:console.error('资源不存在');break;case xhr.status >= 500:console.error('服务器错误');break;default:console.error(`未知错误: ${xhr.status}`);}}
};
九、现代应用场景
1. 大文件分片上传
function uploadFile(file) {const CHUNK_SIZE = 5 * 1024 * 1024; // 5MBconst totalChunks = Math.ceil(file.size / CHUNK_SIZE);for (let i = 0; i < totalChunks; i++) {const start = i * CHUNK_SIZE;const end = Math.min(start + CHUNK_SIZE, file.size);const chunk = file.slice(start, end);const formData = new FormData();formData.append('chunk', chunk);formData.append('chunkIndex', i);formData.append('totalChunks', totalChunks);formData.append('fileName', file.name);const xhr = new XMLHttpRequest();xhr.open('POST', '/upload-chunk', true);xhr.upload.onprogress = function(e) {const chunkProgress = (e.loaded / e.total) * 100;const totalProgress = ((i * CHUNK_SIZE) + e.loaded) / file.size * 100;updateProgress(totalProgress);};xhr.send(formData);}
}
2. 长轮询 (Long Polling)
function longPoll() {const xhr = new XMLHttpRequest();xhr.open('GET', '/api/updates', true);xhr.onreadystatechange = function() {if (xhr.readyState === 4) {if (xhr.status === 200) {processUpdates(JSON.parse(xhr.responseText));}// 无论成功或失败,重新发起请求setTimeout(longPoll, 1000);}};xhr.send();
}// 启动长轮询
longPoll();
十、兼容性与 Polyfill
1. 浏览器支持
- 所有现代浏览器完全支持
- Internet Explorer 7+
- 移动端浏览器全面支持
2. ActiveXObject (IE6及以下)
let xhr;
if (typeof XMLHttpRequest !== 'undefined') {xhr = new XMLHttpRequest();
} else {try {// 旧版IExhr = new ActiveXObject('Msxml2.XMLHTTP.6.0');} catch (e) {try {xhr = new ActiveXObject('Msxml2.XMLHTTP.3.0');} catch (e) {throw new Error('XMLHttpRequest not supported');}}
}
十一、安全注意事项
-
CSRF 防护:
// 添加CSRF令牌 const csrfToken = document.querySelector('meta[name="csrf-token"]').content; xhr.setRequestHeader('X-CSRF-Token', csrfToken);
-
内容安全策略:
<meta http-equiv="Content-Security-Policy" content="default-src 'self';">
-
输入验证:
// 验证服务器响应 if (xhr.responseType === 'document') {const doc = xhr.responseXML;// 防止XXE攻击if (doc.documentElement.nodeName === 'parsererror') {throw new Error('无效的XML响应');} }
总结
XMLHttpRequest 的核心优势:
- 广泛的浏览器支持(包括旧版IE)
- 完善的进度监控(上传/下载)
- 请求取消和超时的原生支持
- 同步请求能力(特定场景需要)
- 成熟的错误处理机制
虽然 Fetch API 提供了更现代的 Promise-based 接口,但 XHR 在以下场景仍不可替代:
- 需要精细控制上传/下载进度
- 兼容旧版浏览器
- 需要同步请求的特殊场景
- 现有项目中使用 XHR 的代码库
理解 XMLHttpRequest 的工作原理和高级用法,对于处理复杂网络请求、维护遗留系统和理解 AJAX 技术演进都至关重要。在现代开发中,可根据具体需求选择 XHR 或 Fetch API,甚至结合两者优势使用。
XMLHttpRequest 与 fetch
XMLHttpRequest 与 Fetch API:全面比较与深度解析
在现代 Web 开发中,XMLHttpRequest (XHR) 和 Fetch API 是两种主要的 HTTP 请求技术。本文将深入比较它们的差异、优缺点和适用场景。
一、核心架构差异
特性 | XMLHttpRequest (XHR) | Fetch API |
---|---|---|
设计范式 | 基于事件回调 | 基于 Promise |
创建时间 | 1999年 (IE5) | 2015年 (ES6) |
标准类型 | 浏览器厂商实现 | W3C 标准 |
基本用法 | const xhr = new XMLHttpRequest() | fetch(url, options) |
二、功能特性对比
1. 请求与响应处理
// XHR 示例
const xhr = new XMLHttpRequest();
xhr.open('GET', '/api/data');
xhr.onload = () => {if (xhr.status === 200) {console.log(JSON.parse(xhr.responseText));}
};
xhr.send();// Fetch 示例
fetch('/api/data').then(response => {if (response.ok) return response.json();throw new Error('请求失败');}).then(data => console.log(data));
2. 进度监控能力
// XHR - 原生支持进度监控
xhr.upload.onprogress = e => {const percent = Math.round((e.loaded / e.total) * 100);console.log(`上传进度: ${percent}%`);
};// Fetch - 需要额外实现
const response = await fetch(url, { method: 'POST',body: readableStreamWithProgress(file) // 自定义进度流
});
3. 超时与取消控制
// XHR - 原生支持
xhr.timeout = 5000; // 5秒超时
xhr.ontimeout = () => console.log('请求超时');
xhr.abort(); // 取消请求// Fetch - 需要 AbortController
const controller = new AbortController();
setTimeout(() => controller.abort(), 5000);fetch(url, {signal: controller.signal
}).catch(e => {if (e.name === 'AbortError') console.log('请求取消');
});
三、关键技术差异详解
1. 错误处理机制
// XHR 需要手动检查状态码
xhr.onreadystatechange = function() {if (xhr.readyState === 4) {if (xhr.status >= 200 && xhr.status < 300) {// 成功处理} else {// 错误处理}}
};// Fetch 不会拒绝HTTP错误(如404)
fetch(url).then(response => {if (!response.ok) { // 必须手动检查throw new Error(`HTTP错误! 状态码: ${response.status}`);}return response.json();}).catch(error => {// 捕获网络错误和手动抛出的错误});
2. 请求体与响应体处理
数据类型 | XHR 支持 | Fetch 支持 |
---|---|---|
JSON | xhr.responseText | response.json() |
文本 | xhr.responseText | response.text() |
Blob | xhr.response | response.blob() |
ArrayBuffer | xhr.response | response.arrayBuffer() |
FormData | 原生支持 | 原生支持 |
流数据 | 有限支持 | 原生支持(ReadableStream) |
3. CORS 与凭证管理
// XHR
xhr.withCredentials = true; // 发送凭据// Fetch
fetch(url, {credentials: 'include' // 包含凭据
});
四、性能与能力对比
能力维度 | XHR | Fetch |
---|---|---|
同步请求 | ✅ 支持 | ❌ 不支持 |
进度事件 | ✅ 原生支持 | ⚠️ 需要自定义实现 |
超时控制 | ✅ 原生支持 | ⚠️ 需要 AbortController + setTimeout |
请求取消 | ✅ xhr.abort() | ✅ AbortController.abort() |
流式处理 | ❌ 有限支持 | ✅ 原生支持(ReadableStream) |
服务端事件(SSE) | ✅ 支持 | ❌ 不支持 |
HTTP/2 支持 | ✅ 完全支持 | ✅ 完全支持 |
五、实际应用场景对比
1. 文件上传(带进度条)
// XHR 实现 (推荐)
const xhr = new XMLHttpRequest();
xhr.upload.onprogress = e => updateProgress(e);
xhr.open('POST', '/upload');
xhr.send(formData);// Fetch 实现(复杂)
// 需要创建自定义可读流实现进度跟踪
2. 大数据流处理
// XHR 有限支持
xhr.responseType = 'arraybuffer';// Fetch 原生流支持(推荐)
fetch('/large-data').then(response => {const reader = response.body.getReader();// 流式处理数据});
3. 简单 API 请求
// Fetch 更简洁(推荐)
fetch('/api/data').then(res => res.json()).then(data => console.log(data));// XHR 实现
const xhr = new XMLHttpRequest();
// ...需要多行代码实现相同功能
六、浏览器兼容性分析
浏览器 | XHR 支持 | Fetch 支持 |
---|---|---|
Chrome | ✅ 全部版本 | ✅ 42+ |
Firefox | ✅ 全部版本 | ✅ 39+ |
Safari | ✅ 全部版本 | ✅ 10.1+ |
Edge | ✅ 全部版本 | ✅ 14+ |
Internet Explorer | ✅ 7+ | ❌ 不支持 |
Node.js | ❌ 不支持 | ✅ 18+ (实验性) |
Polyfill 方案:
<!-- XHR for IE5-6 -->
<script>// 使用 ActiveXObject 实现
</script><!-- Fetch polyfill -->
<script src="https://cdn.jsdelivr.net/npm/whatwg-fetch@3.6.2/dist/fetch.umd.min.js"></script>
七、最佳实践指南
1. 何时选择 XHR
- 需要精确的上传/下载进度监控
- 兼容旧版浏览器(特别是 IE9-11)
- 实现服务器发送事件(SSE)
- 需要同步请求的特殊场景
2. 何时选择 Fetch
- 现代浏览器环境开发
- 需要流式数据处理
- 集成 Service Workers
- 需要更简洁的 Promise 链式调用
- 使用 HTTP/2 高级特性
3. 混合使用策略
// 根据需求选择最佳方案
function httpRequest(url, options = {}) {if (options.needProgress && 'XMLHttpRequest' in window) {return xhrRequest(url, options); // 使用XHR}if ('fetch' in window) {return fetchRequest(url, options); // 使用Fetch}// 降级方案return legacyXhrRequest(url, options);
}
八、未来发展趋势
-
Fetch 的持续增强
- 规范中添加了
priority
属性控制请求优先级
fetch(url, { priority: 'high' }); // 实验性功能
- 规范中添加了
-
HTTP/3 支持
- Fetch API 更易适配新协议
-
WebTransport 协议
// 下一代传输协议 const transport = new WebTransport('https://example.com'); const stream = await transport.createBidirectionalStream();
-
Service Worker 集成
- Fetch 在 PWA 中无缝集成
self.addEventListener('fetch', event => {event.respondWith(caches.match(event.request) || fetch(event.request)); });
总结:核心选择建议
考量因素 | 推荐选择 |
---|---|
现代浏览器项目 | ✅ Fetch (首选) |
需要进度监控 | ✅ XHR |
兼容旧版浏览器 | ✅ XHR |
流数据处理 | ✅ Fetch |
简单API调用 | ✅ Fetch |
大文件上传 | ✅ XHR |
服务端事件(SSE) | ✅ XHR |
PWA/Service Worker | ✅ Fetch |
最终决策指南:
- 优先使用 Fetch API 满足大多数现代 Web 应用需求
- 在需要精确进度控制或兼容旧浏览器时使用 XHR
- 大型项目可封装统一 HTTP 客户端,内部智能选择最佳实现
- 关注 Fetch 规范发展,未来功能将持续增强
两者各有优势,理解其核心差异和适用场景,将帮助您为每个具体需求选择最佳解决方案。
XMLHttpRequest
的response
与 Fetch
的Response
XMLHttpRequest 的 response 与 Fetch 的 Response:深度对比
在 AJAX 请求中,XMLHttpRequest (XHR) 和 Fetch API 处理响应数据的方式有显著差异。以下是两者响应处理的全面对比:
一、核心差异概览
特性 | XMLHttpRequest | Fetch API |
---|---|---|
响应对象 | XHR 实例本身 | 专用 Response 对象 |
数据访问 | 通过属性直接访问 | 通过解析方法获取 |
响应类型设置 | 请求前设置 responseType | 请求后调用解析方法 |
HTTP 错误处理 | 需手动检查 status | 需手动检查 response.ok |
流式处理 | 有限支持 | 原生支持 ReadableStream |
数据复用 | 可多次访问响应属性 | 响应体只能读取一次 |
二、响应数据访问方式对比
1. XMLHttpRequest 的响应访问
const xhr = new XMLHttpRequest();
xhr.open('GET', '/api/data');
xhr.responseType = 'json'; // 预先设置响应类型xhr.onload = function() {// 通过属性直接访问数据if (xhr.status === 200) {console.log('状态:', xhr.status);console.log('响应文本:', xhr.responseText); // 文本形式console.log('解析后数据:', xhr.response); // 根据responseTypeconsole.log('响应头:', xhr.getResponseHeader('Content-Type'));}
};
xhr.send();
2. Fetch 的响应访问
fetch('/api/data').then(response => {console.log('状态:', response.status);console.log('响应头:', response.headers.get('Content-Type'));// 必须调用解析方法获取数据return response.json(); // 返回新Promise}).then(data => {console.log('解析后数据:', data);});
三、关键差异详解
1. 响应数据类型处理
数据类型 | XMLHttpRequest | Fetch API |
---|---|---|
文本 | xhr.responseText | response.text() |
JSON | xhr.response (需设 responseType='json' ) | response.json() |
Blob | xhr.response (需设 responseType='blob' ) | response.blob() |
ArrayBuffer | xhr.response (需设 responseType='arraybuffer' ) | response.arrayBuffer() |
文档 | xhr.responseXML | 无直接等效,需 response.text() 后解析 |
FormData | 无原生支持 | response.formData() |
2. 响应头访问对比
// XMLHttpRequest
const contentType = xhr.getResponseHeader('Content-Type');
const allHeaders = xhr.getAllResponseHeaders();// Fetch API
const contentType = response.headers.get('Content-Type');
const headersObj = Object.fromEntries(response.headers.entries());
3. HTTP 错误处理差异
// XMLHttpRequest
xhr.onreadystatechange = function() {if (xhr.readyState === 4) {if (xhr.status >= 200 && xhr.status < 300) {// 成功处理} else {// HTTP错误处理}}
};// Fetch API
fetch(url).then(response => {if (!response.ok) { // 手动检查throw new Error(`HTTP错误! 状态码: ${response.status}`);}return response.json();}).catch(error => {// 捕获网络错误和手动抛出的错误});
4. 数据复用能力
// XMLHttpRequest - 可多次访问
console.log(xhr.responseText); // 无限制
console.log(xhr.response);// Fetch API - 响应体只能读取一次
fetch(url).then(response => {const dataPromise = response.json(); // 首次读取// 尝试再次读取会报错response.text() // TypeError: Already read.catch(e => console.error(e));});
5. 流式数据处理
// XMLHttpRequest - 有限支持
xhr.responseType = 'arraybuffer';
xhr.onprogress = function(e) {if (e.lengthComputable) {const partialData = new Uint8Array(xhr.response);}
};// Fetch API - 原生流支持
fetch('/large-file').then(response => {const reader = response.body.getReader();function processStream({ done, value }) {if (done) return;console.log('收到数据块:', value);return reader.read().then(processStream);}return reader.read().then(processStream);});
四、特殊场景处理
1. 二进制数据处理
// XMLHttpRequest
xhr.responseType = 'arraybuffer';
xhr.onload = function() {const buffer = xhr.response;const dataView = new DataView(buffer);
};// Fetch API
fetch('/binary-data').then(response => response.arrayBuffer()).then(buffer => {const dataView = new DataView(buffer);});
2. 超大型文件处理
// Fetch 流式处理更高效 (内存占用恒定)
async function processLargeFile(url) {const response = await fetch(url);const reader = response.body.getReader();let receivedLength = 0;while (true) {const { done, value } = await reader.read();if (done) break;// 处理数据块receivedLength += value.length;console.log(`已接收 ${receivedLength} 字节`);}
}
3. 服务端发送事件 (SSE)
// XMLHttpRequest 支持
const xhr = new XMLHttpRequest();
xhr.open('GET', '/sse-endpoint');
xhr.onprogress = function() {console.log('收到部分数据:', xhr.responseText);
};// Fetch API 不支持原生 SSE
// 需使用专用 EventSource API
const es = new EventSource('/sse-endpoint');
es.onmessage = event => {console.log('收到事件:', event.data);
};
五、性能与内存对比
指标 | XMLHttpRequest | Fetch API |
---|---|---|
内存占用 | 整个响应存储在内存中 | 支持流式处理,内存占用更低 |
大文件处理 | 可能内存溢出 | 适合处理超大文件 |
响应延迟 | 需等待整个响应完成 | 可流式处理首批数据 |
GC 效率 | 整个响应对象统一回收 | 分块数据可及时回收 |
六、最佳实践指南
1. 选择建议:
-
使用 XMLHttpRequest 当:
- 需要精确的上传/下载进度
- 兼容 IE11 等旧浏览器
- 处理小到中型数据
- 需要同步请求(特殊场景)
-
使用 Fetch API 当:
- 处理大型文件或流数据
- 现代浏览器环境
- 需要更好的错误处理链
- 集成 Service Worker
2. 混合使用方案:
async function smartFetch(url, options = {}) {// 需要进度监控时使用 XHRif (options.trackProgress) {return new Promise((resolve, reject) => {const xhr = new XMLHttpRequest();xhr.open(options.method || 'GET', url);// 进度处理if (options.onProgress) {xhr.upload.onprogress = options.onProgress;}xhr.onload = () => resolve({status: xhr.status,data: xhr.response,headers: xhr.getAllResponseHeaders()});xhr.onerror = reject;xhr.send(options.body);});}// 默认使用 Fetchtry {const response = await fetch(url, options);const data = await response.json();return { status: response.status, data };} catch (error) {throw error;}
}
七、未来发展趋势
-
Fetch 的增强功能:
// 实验性优先级控制 fetch(url, { priority: 'high' });// 请求诊断API const timing = performance.getEntriesByName(url)[0]; console.log('TTFB:', timing.responseStart - timing.requestStart);
-
WebTransport API:
// 下一代HTTP/3传输协议 const transport = new WebTransport('https://example.com'); const stream = await transport.createBidirectionalStream();
-
响应缓存优化:
fetch(url, {cache: 'force-cache', // 强缓存integrity: 'sha256-...' // 子资源完整性校验 });
总结:核心选择建议
-
响应数据访问:
- XHR:直接访问响应属性,更简单
- Fetch:需要调用解析方法,更现代
-
大文件处理:
- XHR:有限支持,内存占用高
- Fetch:原生流支持,内存效率高
-
错误处理:
- XHR:需手动检查状态码
- Fetch:需检查
response.ok
-
浏览器支持:
- XHR:全平台支持(包括 IE7+)
- Fetch:现代浏览器(IE 不支持)
根据具体需求选择:
- 简单请求/旧浏览器 → XMLHttpRequest
- 流处理/现代浏览器 → Fetch API
- 混合环境 → 使用抽象层封装
理解这些核心差异将帮助您为特定场景选择最合适的 HTTP 请求技术。
fetch 用例
Fetch API 实用用例大全:从基础到高级实践
Fetch API 是现代 Web 开发的基石,以下是 10 个关键场景的实用代码示例,涵盖从基础请求到高级应用:
一、基础请求场景
1. GET 请求 - 获取 JSON 数据
// 获取用户数据
async function getUsers() {try {const response = await fetch('https://api.example.com/users');if (!response.ok) {throw new Error(`HTTP 错误! 状态码: ${response.status}`);}const users = await response.json();console.log('用户列表:', users);return users;} catch (error) {console.error('获取用户失败:', error);return [];}
}
2. POST 请求 - 提交表单数据
// 创建新用户
async function createUser(userData) {try {const response = await fetch('https://api.example.com/users', {method: 'POST',headers: {'Content-Type': 'application/json','Authorization': 'Bearer YOUR_TOKEN'},body: JSON.stringify(userData),credentials: 'include' // 包含 cookies});if (response.status === 201) {const newUser = await response.json();console.log('用户创建成功:', newUser);return newUser;} else {const errorData = await response.json();throw new Error(errorData.message || '创建用户失败');}} catch (error) {console.error('创建用户错误:', error);throw error;}
}// 使用示例
createUser({ name: 'Alice', email: 'alice@example.com' });
二、文件处理场景
3. 上传文件
async function uploadFile(file) {const formData = new FormData();formData.append('file', file);formData.append('description', '用户头像');try {const response = await fetch('/api/upload', {method: 'POST',body: formData,// 不需要手动设置 Content-Type,浏览器会自动设置 multipart/form-data});if (!response.ok) {throw new Error('文件上传失败');}const result = await response.json();console.log('文件上传成功:', result.fileUrl);return result.fileUrl;} catch (error) {console.error('上传错误:', error);throw error;}
}// 使用示例
const fileInput = document.querySelector('input[type="file"]');
fileInput.addEventListener('change', async (e) => {if (e.target.files[0]) {await uploadFile(e.target.files[0]);}
});
4. 下载文件
async function downloadFile(fileUrl, fileName) {try {const response = await fetch(fileUrl);if (!response.ok) {throw new Error('文件下载失败');}const blob = await response.blob();const url = window.URL.createObjectURL(blob);// 创建临时链接并触发下载const a = document.createElement('a');a.href = url;a.download = fileName || 'downloaded-file';document.body.appendChild(a);a.click();// 清理setTimeout(() => {window.URL.revokeObjectURL(url);document.body.removeChild(a);}, 100);} catch (error) {console.error('下载错误:', error);alert('文件下载失败');}
}// 使用示例
downloadFile('https://example.com/report.pdf', '季度报告.pdf');
三、高级控制场景
5. 请求超时处理
async function fetchWithTimeout(url, options = {}, timeout = 8000) {const controller = new AbortController();const timeoutId = setTimeout(() => controller.abort(), timeout);try {const response = await fetch(url, {...options,signal: controller.signal});clearTimeout(timeoutId);if (!response.ok) {throw new Error(`请求失败: ${response.status}`);}return response;} catch (error) {if (error.name === 'AbortError') {throw new Error(`请求超时 (${timeout}ms)`);}throw error;}
}// 使用示例
fetchWithTimeout('https://slow-api.com/data', {}, 5000).then(response => response.json()).then(data => console.log(data)).catch(error => console.error('错误:', error.message));
6. 并发请求处理
async function fetchMultiple(urls) {try {const requests = urls.map(url => fetch(url).then(response => {if (!response.ok) throw new Error(`请求失败: ${url}`);return response.json();}));const results = await Promise.allSettled(requests);// 处理结果const data = [];const errors = [];results.forEach(result => {if (result.status === 'fulfilled') {data.push(result.value);} else {errors.push(result.reason.message);}});console.log('成功请求:', data.length);console.log('失败请求:', errors);return { data, errors };} catch (error) {console.error('并发请求错误:', error);return { data: [], errors: [error.message] };}
}// 使用示例
fetchMultiple(['https://api.example.com/users','https://api.example.com/posts','https://api.example.com/comments'
]);
四、特殊场景处理
7. 处理流式数据(大文件/实时数据)
async function processLargeFile(url, onChunk, onComplete) {try {const response = await fetch(url);if (!response.ok) {throw new Error('无法获取文件');}const reader = response.body.getReader();const decoder = new TextDecoder();let receivedLength = 0;while (true) {const { done, value } = await reader.read();if (done) {onComplete();break;}receivedLength += value.length;const chunk = decoder.decode(value, { stream: true });// 处理数据块onChunk(chunk, receivedLength);}} catch (error) {console.error('流处理错误:', error);}
}// 使用示例
processLargeFile('https://example.com/large-log.txt',(chunk, received) => {console.log(`已接收 ${received} 字节`);// 实时处理数据块},() => console.log('文件处理完成')
);
8. 带进度的文件上传
async function uploadWithProgress(file, onProgress) {const controller = new AbortController();try {const response = await fetch('/api/upload', {method: 'POST',headers: {'X-File-Name': encodeURIComponent(file.name),'X-File-Size': file.size},body: file,signal: controller.signal,duplex: 'half' // 必需参数});if (!response.ok) {throw new Error('上传失败');}return await response.json();} catch (error) {if (error.name !== 'AbortError') {console.error('上传错误:', error);}throw error;}
}// 创建带进度监控的可读流
function trackUploadProgress(file, onProgress) {let uploaded = 0;const total = file.size;const trackedFile = new ReadableStream({start(controller) {const reader = new FileReader();reader.onload = function() {controller.enqueue(new Uint8Array(reader.result));uploaded += reader.result.byteLength;onProgress(uploaded, total);controller.close();};const chunkSize = 1024 * 1024; // 1MB 分块let offset = 0;function readNextChunk() {const chunk = file.slice(offset, offset + chunkSize);reader.readAsArrayBuffer(chunk);offset += chunkSize;if (offset >= file.size) {controller.close();}}readNextChunk();}});return new File([trackedFile], file.name, {type: file.type,lastModified: file.lastModified});
}// 使用示例
const fileInput = document.getElementById('fileInput');
fileInput.addEventListener('change', async (e) => {const file = e.target.files[0];if (file) {const trackedFile = trackUploadProgress(file, (uploaded, total) => {const percent = Math.round((uploaded / total) * 100);console.log(`上传进度: ${percent}%`);});await uploadWithProgress(trackedFile);console.log('文件上传完成');}
});
五、安全与优化场景
9. 带重试机制的请求
async function fetchWithRetry(url, options = {}, maxRetries = 3, delay = 1000) {for (let attempt = 1; attempt <= maxRetries; attempt++) {try {const response = await fetch(url, options);// 服务器错误时重试if (response.status >= 500 && attempt < maxRetries) {throw new Error(`服务器错误: ${response.status}`);}if (!response.ok) {throw new Error(`请求失败: ${response.status}`);}return response;} catch (error) {if (attempt === maxRetries) throw error;// 指数退避算法const waitTime = delay * Math.pow(2, attempt - 1);console.warn(`请求失败,${waitTime}ms后重试 (${attempt}/${maxRetries})`);await new Promise(resolve => setTimeout(resolve, waitTime));}}
}// 使用示例
fetchWithRetry('https://unstable-api.com/data', {}, 5).then(response => response.json()).then(data => console.log('最终数据:', data)).catch(error => console.error('所有重试失败:', error));
10. 请求拦截器(全局处理)
// 保存原始 fetch 引用
const originalFetch = window.fetch;// 自定义 fetch 实现
window.fetch = async function(url, options = {}) {// 1. 请求前处理console.log(`请求发起: ${url}`);// 自动添加认证令牌if (!options.headers) options.headers = {};if (!options.headers['Authorization']) {const token = localStorage.getItem('authToken');if (token) {options.headers['Authorization'] = `Bearer ${token}`;}}// 添加请求时间戳options.headers['X-Request-Timestamp'] = Date.now();try {// 2. 发起实际请求const response = await originalFetch(url, options);// 3. 响应后处理console.log(`请求完成: ${url} [${response.status}]`);// 自动刷新令牌if (response.status === 401) {const refreshed = await refreshToken();if (refreshed) {// 使用新令牌重试请求return window.fetch(url, options);}}return response;} catch (error) {// 4. 错误处理console.error(`请求失败: ${url}`, error);// 全局错误通知showNotification(`网络错误: ${error.message}`);throw error;}
};// 使用示例
// 所有 fetch 请求将自动通过此拦截器
fetch('https://api.example.com/data').then(response => response.json()).then(data => console.log(data));// 辅助函数
async function refreshToken() {const refreshToken = localStorage.getItem('refreshToken');if (!refreshToken) return false;try {const response = await originalFetch('/api/refresh-token', {method: 'POST',headers: {'Content-Type': 'application/json'},body: JSON.stringify({ refreshToken })});if (response.ok) {const { accessToken } = await response.json();localStorage.setItem('authToken', accessToken);return true;}} catch (error) {console.error('令牌刷新失败:', error);}// 刷新失败,清除用户状态localStorage.removeItem('authToken');localStorage.removeItem('refreshToken');return false;
}
六、实用封装函数
11. 通用请求封装
class ApiClient {constructor(baseUrl, defaultOptions = {}) {this.baseUrl = baseUrl;this.defaultOptions = {headers: {'Content-Type': 'application/json','Accept': 'application/json'},...defaultOptions};}async request(endpoint, options = {}) {const url = `${this.baseUrl}${endpoint}`;const config = {...this.defaultOptions,...options,headers: {...this.defaultOptions.headers,...(options.headers || {})}};if (config.body && typeof config.body === 'object' && config.headers['Content-Type'] === 'application/json') {config.body = JSON.stringify(config.body);}try {const response = await fetch(url, config);// 处理非200响应if (!response.ok) {let errorData;try {errorData = await response.json();} catch {errorData = { message: `HTTP错误 ${response.status}` };}throw new ApiError(response.status, errorData);}// 处理空响应if (response.status === 204) {return null;}// 根据内容类型解析响应const contentType = response.headers.get('content-type') || '';if (contentType.includes('application/json')) {return await response.json();} else if (contentType.includes('text/')) {return await response.text();} else {return await response.blob();}} catch (error) {if (error instanceof ApiError) throw error;throw new NetworkError(error.message);}}get(endpoint, options = {}) {return this.request(endpoint, { ...options, method: 'GET' });}post(endpoint, body, options = {}) {return this.request(endpoint, { ...options, method: 'POST', body });}put(endpoint, body, options = {}) {return this.request(endpoint, { ...options, method: 'PUT', body });}delete(endpoint, options = {}) {return this.request(endpoint, { ...options, method: 'DELETE' });}
}// 自定义错误类
class ApiError extends Error {constructor(status, data) {super(data.message || `API请求失败: ${status}`);this.name = 'ApiError';this.status = status;this.data = data;}
}class NetworkError extends Error {constructor(message) {super(message || '网络错误');this.name = 'NetworkError';}
}// 使用示例
const api = new ApiClient('https://api.example.com', {credentials: 'include'
});// 获取用户
api.get('/users/123').then(user => console.log('用户:', user)).catch(error => {if (error instanceof ApiError) {console.error(`API错误 [${error.status}]:`, error.data);} else {console.error('网络错误:', error);}});// 创建新项目
api.post('/projects', { title: '新项目', description: '项目描述' }).then(project => console.log('创建的项目:', project));
关键要点总结
- 基础请求:使用
fetch().then().catch()
模式处理基本请求 - 错误处理:始终检查
response.ok
或response.status
- 超时控制:使用
AbortController
实现请求超时 - 文件处理:
- 上传:使用
FormData
或二进制流 - 下载:使用
blob()
和对象 URL
- 上传:使用
- 高级场景:
- 流处理:使用
ReadableStream
处理大数据 - 进度监控:自定义可读流实现
- 重试机制:指数退避算法提升可靠性
- 流处理:使用
- 安全实践:
- 自动添加认证头
- 令牌刷新机制
- 全局错误处理
- 封装模式:创建可复用的 API 客户端类
这些实用代码片段可直接集成到项目中,覆盖了大多数网络请求场景的最佳实践。根据具体需求调整参数和错误处理逻辑,可以构建健壮高效的网络请求系统。