前言
在现代Web开发中,客户端与服务器之间的异步通信是构建动态应用的核心能力。无论是传统的AJAX技术(基于XMLHttpRequest
)还是现代的Fetch API
,它们都为实现这一目标提供了关键支持。本文将从底层原理、核心功能、代码实践到实际应用场景,系统性地对比和分析这两种API的异同,帮助你掌握如何在不同场景下选择最佳解决方案。
文章目录
- 前言
- 一、XMLHttpRequest API:传统异步请求的基石
- 1.1 核心概念与工作原理
- 1.2 关键功能与高级用法
- 1.3 局限性分析
- 二、Fetch API:现代Web请求的标准方案
- 2.1 设计理念与核心优势
- 2.2 基础用法与代码示例
- 2.3 高级功能实践
- 2.4 常见问题与解决方案
- 三、XMLHttpRequest与Fetch API的深度对比
- 3.1 功能特性对比
- 3.2 性能与适用场景
- 四、实战案例:构建一个健壮的HTTP客户端
- 4.1 基于Fetch的封装库
- 4.2 实现文件分片上传
- 总结
一、XMLHttpRequest API:传统异步请求的基石
1.1 核心概念与工作原理
XMLHttpRequest
(XHR)是浏览器提供的原生API,用于在不刷新页面的情况下与服务器交换数据。其核心流程包括:
- 实例化对象:通过构造函数创建
XMLHttpRequest
实例。 - 配置请求:指定HTTP方法、URL及是否异步。
- 绑定事件:监听请求状态变化(如
onload
、onerror
)。 - 发送请求:调用
send()
方法并处理响应数据。
const xhr = new XMLHttpRequest();
xhr.open('GET', 'https://api.example.com/data', true);
xhr.onload = function() {
if (xhr.status === 200) {
console.log(JSON.parse(xhr.responseText));
} else {
console.error('请求失败:', xhr.status);
}
};
xhr.onerror = function() {
console.error('网络错误');
};
xhr.send();
1.2 关键功能与高级用法
-
同步与异步模式
// 同步请求(阻塞主线程,不推荐) xhr.open('GET', 'https://api.example.com/data', false); xhr.send(); console.log(xhr.responseText);
-
处理二进制数据
xhr.responseType = 'arraybuffer'; xhr.onload = function() { const buffer = xhr.response; // 处理二进制数据(如图像或文件) };
-
上传进度监控
xhr.upload.onprogress = function(event) { const percent = (event.loaded / event.total) * 100; console.log(`上传进度: ${percent}%`); };
-
超时控制
xhr.timeout = 5000; // 5秒超时 xhr.ontimeout = function() { console.error('请求超时'); };
1.3 局限性分析
- 回调地狱:事件监听机制导致代码嵌套复杂。
- 错误处理不统一:需手动检查HTTP状态码和网络错误。
- 不支持Promise:与现代异步编程模式不兼容。
二、Fetch API:现代Web请求的标准方案
2.1 设计理念与核心优势
Fetch API
基于Promise设计,提供更简洁、灵活的请求方式,并天然支持以下特性:
- 链式调用:避免回调嵌套。
- Streams API集成:处理流式数据(如大文件下载)。
- CORS与安全策略:默认不发送跨域Cookie,需显式配置。
2.2 基础用法与代码示例
fetch('https://api.example.com/data')
.then(response => {
if (!response.ok) throw new Error('HTTP错误');
return response.json();
})
.then(data => console.log(data))
.catch(error => console.error('请求失败:', error));
2.3 高级功能实践
-
自定义请求头与模式
fetch('https://api.example.com/data', { method: 'POST', headers: { 'Content-Type': 'application/json', 'Authorization': 'Bearer token' }, body: JSON.stringify({ key: 'value' }), mode: 'cors', // 跨域模式 credentials: 'include' // 包含Cookie });
-
中断请求(AbortController)
const controller = new AbortController(); setTimeout(() => controller.abort(), 5000); fetch('https://api.example.com/data', { signal: controller.signal }).catch(error => { if (error.name === 'AbortError') { console.log('请求被手动取消'); } });
-
流式数据处理
fetch('https://api.example.com/large-file') .then(response => { const reader = response.body.getReader(); return new ReadableStream({ start(controller) { function push() { reader.read().then(({ done, value }) => { if (done) { controller.close(); return; } controller.enqueue(value); push(); }); } push(); } }); }) .then(stream => new Response(stream)) .then(response => response.blob());
2.4 常见问题与解决方案
-
错误处理优化:
async function fetchWithRetry(url, retries = 3) { for (let i = 0; i < retries; i++) { try { const response = await fetch(url); if (!response.ok) throw new Error('HTTP错误'); return await response.json(); } catch (error) { if (i === retries - 1) throw error; await new Promise(resolve => setTimeout(resolve, 1000 * (i + 1))); } } }
-
超时封装:
function fetchWithTimeout(url, timeout = 5000) { return Promise.race([ fetch(url), new Promise((_, reject) => setTimeout(() => reject(new Error('请求超时')), timeout) ) ]); }
三、XMLHttpRequest与Fetch API的深度对比
3.1 功能特性对比
特性 | XMLHttpRequest | Fetch API |
---|---|---|
Promise支持 | 否 | 是 |
请求取消 | 是(abort()) | 是(AbortController) |
流式数据处理 | 有限支持 | 完全支持 |
CORS处理 | 需手动配置 | 默认安全策略 |
Service Worker | 不支持 | 完全集成 |
3.2 性能与适用场景
-
XMLHttpRequest适用场景:
- 需要监控上传/下载进度。
- 兼容旧版浏览器(如IE10及以下)。
-
Fetch API推荐场景:
- 现代Web应用开发。
- 需要与Service Worker配合实现离线缓存。
- 处理流式数据或大文件。
四、实战案例:构建一个健壮的HTTP客户端
4.1 基于Fetch的封装库
class HttpClient {
constructor(baseURL, headers = {}) {
this.baseURL = baseURL;
this.headers = headers;
}
async request(endpoint, options = {}) {
const url = `${this.baseURL}${endpoint}`;
const response = await fetch(url, {
...options,
headers: { ...this.headers, ...options.headers }
});
if (!response.ok) {
const error = new Error(`HTTP ${response.status}`);
error.response = response;
throw error;
}
const contentType = response.headers.get('content-type');
if (contentType?.includes('application/json')) {
return response.json();
}
return response.text();
}
get(endpoint, params) {
const query = new URLSearchParams(params).toString();
return this.request(`${endpoint}?${query}`, { method: 'GET' });
}
post(endpoint, body) {
return this.request(endpoint, {
method: 'POST',
body: JSON.stringify(body),
headers: { 'Content-Type': 'application/json' }
});
}
}
// 使用示例
const api = new HttpClient('https://api.example.com', {
Authorization: 'Bearer token'
});
api.get('/users', { page: 1 }).then(users => console.log(users));
4.2 实现文件分片上传
async function uploadFile(file, chunkSize = 1024 * 1024) {
const totalChunks = Math.ceil(file.size / chunkSize);
for (let i = 0; i < totalChunks; i++) {
const start = i * chunkSize;
const end = Math.min(start + chunkSize, file.size);
const chunk = file.slice(start, end);
const formData = new FormData();
formData.append('file', chunk);
formData.append('chunkIndex', i);
formData.append('totalChunks', totalChunks);
await fetch('/upload', {
method: 'POST',
body: formData
});
}
}
总结
XMLHttpRequest作为Web异步通信的奠基者,至今仍在特定场景下发挥作用,而Fetch API凭借其现代化的设计正在成为主流选择。开发者需要根据以下因素决策:
- 浏览器兼容性:如需支持旧版浏览器,XHR仍是必要选项。
- 功能需求:进度监控、请求取消等特性可能影响技术选型。
- 代码可维护性:Fetch的Promise链与async/await语法更易维护。
未来,随着Web Streams API和Service Worker的普及,Fetch API将在性能优化和离线体验领域展现更大潜力。建议在新项目中优先采用Fetch,同时保持对XHR原理的理解以应对遗留系统维护需求。