XMLHttpRequest 和 fetch 的区别是什么?
XMLHttpRequest (XHR) 和 fetch 都是用于在浏览器中发起网络请求(AJAX)的 API,但它们属于不同的时代,设计理念也有很大区别。
简单来说:XMLHttpRequest 是老旧的、基于事件的 API;而 fetch 是现代的、基于 Promise 的 API。
以下是它们的主要区别:
1. 语法与设计模式 (Syntax & Design)
- XMLHttpRequest:
- 基于事件 (Event-based): 你需要监听
onreadystatechange或onload等事件来处理结果。 - 回调地狱: 在处理多个依赖请求时,容易导致代码嵌套过深,难以维护。
- 配置繁琐,对象状态复杂。
- 基于事件 (Event-based): 你需要监听
- Fetch:
- 基于 Promise: 原生支持 Promise,可以链式调用
.then(),或者配合async/await使用,代码更加简洁、线性。 - 设计更加模块化(Request, Response, Headers 对象分离)。
- 基于 Promise: 原生支持 Promise,可以链式调用
2. 错误处理 (Error Handling) —— 核心区别
这是开发者从 XHR 转到 Fetch 时最容易踩的坑。
- XMLHttpRequest:
- 如果请求失败(网络错误)或者服务器返回错误状态码(如 404, 500),你通常需要在回调函数里手动判断
status。
- 如果请求失败(网络错误)或者服务器返回错误状态码(如 404, 500),你通常需要在回调函数里手动判断
- Fetch:
- Fetch 认为只要服务器返回了响应,请求就是成功的。
- 即便服务器返回 404 或 500 错误,Fetch 返回的 Promise 状态依然是 resolved (成功)。
- Fetch 只有在网络故障(如 DNS 解析失败、断网)或请求被阻止时,Promise 才会 reject (失败)。
- 处理方式: 你必须手动检查
response.ok属性(当 status 在 200-299 之间时为 true)来判断业务逻辑是否成功。
3. Cookie 和凭证 (Cookies & Credentials)
- XMLHttpRequest:
- 默认情况下,同源请求会自动携带 Cookie。跨域请求如果配置了
withCredentials = true也会携带。
- 默认情况下,同源请求会自动携带 Cookie。跨域请求如果配置了
- Fetch:
- 默认不发送 Cookie(即使是同源请求,早期标准也不发送,现代标准同源默认发送,但跨域绝对不发)。
- 如果你需要发送 Cookie(特别是跨域时),必须显式配置
credentials: 'include'。
4. 数据处理 (Data Handling)
- XMLHttpRequest:
- 处理 JSON 数据比较麻烦,通常需要手动
JSON.parse(xhr.responseText)。
- 处理 JSON 数据比较麻烦,通常需要手动
- Fetch:
- 提供了方便的方法直接转换数据流,如
response.json(),response.text(),response.blob(),response.formData()等。
- 提供了方便的方法直接转换数据流,如
5. 进度监测 (Progress Monitoring)
- XMLHttpRequest:
- 优势: 原生支持上传和下载的进度监控 (
onprogress事件)。这在做文件上传进度条时非常有用。
- 优势: 原生支持上传和下载的进度监控 (
- Fetch:
- 劣势: Fetch 本身没有原生的
onprogress事件。 - 虽然可以通过
response.body.getReader()(ReadableStream) 来实现下载进度监控,但实现起来比 XHR 复杂得多。 - 上传进度目前 Fetch 标准支持依然不完善(通常还是退回到 XHR 处理大文件上传)。
- 劣势: Fetch 本身没有原生的
6. 请求中止 (Aborting Requests)
- XMLHttpRequest:
- 直接调用
xhr.abort()即可,非常简单。
- 直接调用
- Fetch:
- 需要配合
AbortController和signal来实现,稍微繁琐一点,但这是通用的取消异步操作的标准。
- 需要配合
7. 兼容性 (Compatibility)
- XMLHttpRequest: 兼容所有浏览器,包括古老的 IE。
- Fetch: 现代浏览器支持良好,但在 IE 中无法使用(需要引入 Polyfill,如
whatwg-fetch)。
代码对比
XMLHttpRequest 写法
javascript
const xhr = new XMLHttpRequest();
xhr.open('GET', '/api/data');
xhr.onload = function() {
if (xhr.status >= 200 && xhr.status < 300) {
// 成功,手动解析 JSON
const data = JSON.parse(xhr.responseText);
console.log(data);
} else {
// 处理 HTTP 错误 (404, 500 等)
console.error('Error:', xhr.statusText);
}
};
xhr.onerror = function() {
// 处理网络错误
console.error('Network Error');
};
xhr.send();
Fetch 写法 (使用 async/await)
javascript
async function getData() {
try {
const response = await fetch('/api/data', {
// 如果需要携带 cookie
// credentials: 'include'
});
// 关键:手动检查 HTTP 状态码
if (!response.ok) {
throw new Error(`HTTP error! status: ${response.status}`);
}
// 自动解析 JSON
const data = await response.json();
console.log(data);
} catch (error) {
// 捕获网络错误 或 上面抛出的 HTTP 错误
console.error('Fetch failed:', error);
}
}
getData();
总结:该用哪个?
- 绝大多数情况:使用
fetch(或者基于 fetch 封装的库,如 Axios)。它更现代、代码更易读、功能更强大。 - 特殊情况:使用
XMLHttpRequest。如果你需要监控上传进度(例如上传大文件并显示进度条),或者必须兼容非常老的浏览器且不想用 Polyfill,XHR 依然是唯一的选择。