JavaScript AJAX:XMLHttpRequest 与 fetch API
AJAX(Asynchronous JavaScript and XML)允许网页在不重新加载整个页面的情况下,与服务器交换数据并更新部分内容。这是现代 Web 应用交互的核心。目前,实现 AJAX 主要有两种方式:传统的 XMLHttpRequest (XHR) 和现代的 fetch API。
一、 XMLHttpRequest (XHR):传统的基石
XMLHttpRequest 是实现 AJAX 的原始技术。尽管它名称中包含 XML,但它可以获取任何类型的数据。虽然代码写起来相对繁琐,但它在所有旧版浏览器中都有极好的兼容性。
使用 XHR 获取数据通常包含以下步骤:
- 创建 一个
XMLHttpRequest对象。 - 配置 请求参数(如方法、URL)。
- 监听 状态变化事件。
- 发送 请求。
具体代码实现如下:
// 1. 创建对象
const xhr = new XMLHttpRequest();
// 2. 监听状态变化
// readyState: 0=未初始化, 1=载入中, 2=载入完成, 3=解析中, 4=完成
xhr.onreadystatechange = function() {
// 检查请求是否完成 (readyState 4) 且成功 (status 200)
if (xhr.readyState === 4 && xhr.status === 200) {
// 5. 处理返回的数据
console.log(xhr.responseText);
}
};
// 3. 配置请求:GET 方法,目标 URL,异步执行
xhr.open('GET', 'https://jsonplaceholder.typicode.com/posts/1', true);
// 4. 发送请求
xhr.send();
二、 fetch API:现代的标准
fetch 是基于 Promise 设计的 API,旨在取代 XMLHttpRequest。它语法更简洁,逻辑更清晰,且原生支持流式处理。fetch 返回一个 Promise 对象,这使得链式调用和 async/await 语法变得非常容易。
使用 fetch 获取数据通常包含以下步骤:
- 调用
fetch()函数并传入 URL。 - 使用
.then()或await处理返回的 Promise。 - 解析 响应体(如
.json())。 - 处理 最终数据。
具体代码实现如下:
// 使用 Promise 链式调用
fetch('https://jsonplaceholder.typicode.com/posts/1')
.then(response => {
// 检查网络请求是否成功
if (!response.ok) {
throw new Error('网络响应异常');
}
// 解析 JSON 数据
return response.json();
})
.then(data => {
console.log(data);
})
.catch(error => {
console.error('请求失败:', error);
});
推荐使用 async/await 语法,因为它更接近同步代码的逻辑,可读性更高:
async function getData() {
try {
// 1. 发起请求并等待响应
const response = await fetch('https://jsonplaceholder.typicode.com/posts/1');
// 2. 检查 HTTP 状态码
if (!response.ok) {
throw new Error(`HTTP 错误! 状态: ${response.status}`);
}
// 3. 解析 JSON 数据
const data = await response.json();
// 4. 使用数据
console.log(data);
} catch (error) {
console.error('获取数据出错:', error);
}
}
getData();
```
---
### 三、 核心差异对比
理解两者的区别有助于在不同的项目场景中做出正确选择。
| 特性 | XMLHttpRequest (XHR) | fetch API |
| :--- | :--- | :--- |
| **语法风格** | 回调函数,容易产生“回调地狱” | Promise / async-await,代码扁平清晰 |
| **浏览器支持** | 支持所有旧版浏览器 (IE7+) | 现代浏览器 (IE 不支持,需 Polyfill) |
| **JSON 处理** | 需手动 `JSON.parse()` | 内置 `.json()` 方法,自动解析 |
| **错误处理** | 需结合 `onerror` 和 `status` 判断 | 网络错误走 `catch`,HTTP 错误 (如 404) 需手动检查 `response.ok` |
| **底层控制** | 控制力强,可监听上传/下载进度 | 基础版本不直接支持进度监听 (需使用 Response.body) |
**关于 XHR 进度监听的补充说明:**
如果你需要监听文件上传的进度条,`XMLHttpRequest` 提供了 `upload.onprogress` 事件,这在使用 `fetch` 时较难直接实现。
```javascript
xhr.upload.onprogress = function(event) {
if (event.lengthComputable) {
const percentComplete = (event.loaded / event.total) * 100;
console.log('上传进度: ' + percentComplete + '%');
}
};
```
---
### 四、 fetch 的常见陷阱与应对
在使用 `fetch` 时,最大的误区是认为只要 `catch` 到了错误就代表请求完全失败了。
**关键点:** 只有网络错误(如断网、DNS 解析失败)才会触发 `Promise.reject` 进入 `catch`。如果服务器返回了 HTTP 状态码(如 `404 Not Found` 或 `500 Server Error`),`fetch` 会认为请求“成功”完成(因为 TCP 握手成功了),从而进入 `then` 分支。
因此,**务必检查** `response.ok` 属性或 `response.status`。
**错误的写法(可能吞掉 404/500 错误):**
```javascript
fetch('/api/wrong-url')
.then(response => response.json()) // 404 页面可能不是合法 JSON,这里可能会报错
.catch(error => console.log(error)); // 仅捕获网络错误
```
**正确的写法:**
```javascript
fetch('/api/wrong-url')
.then(response => {
if (!response.ok) {
// 手动抛出错误,使其进入 catch
throw new Error(`状态码 ${response.status}`);
}
return response.json();
})
.then(data => console.log(data))
.catch(error => console.error('请求或处理出错:', error));
五、 请求流程解析
为了更直观地理解 fetch 处理请求和响应的完整逻辑,以下是标准的异步数据获取流程图:
graph TD
A["Start: Call fetch url"] --> B{Network OK?}
B -- "No (Offline)" --> C["Catch Error"]
C --> F["End"]
B -- "Yes" --> D["Receive Response Object"]
D --> E{"HTTP Status OK? (200-299)"}
E -- "No (e.g. 404, 500)" --> G["Throw Error manually"]
G --> C
E -- "Yes" --> H["Parse Data: response.json"]
H --> I["Use Data in Logic"]
I --> F
六、 实际应用建议
在以下情况使用 fetch:
- 项目主要针对现代浏览器或移动端 Web 应用。
- 代码结构需要简洁,大量使用了
async/await。 - 需要处理流式数据。
在以下情况使用 XMLHttpRequest:
- 需要支持 Internet Explorer 等老旧浏览器。
- 需要精确监听上传或下载的进度事件。
- 需要同步请求(虽然极度不推荐,但在极少数特定场景下 XHR 支持同步模式)。
总结用法示例(POST 请求):
在使用 fetch 发送 POST 请求时,必须设置请求头并序列化请求体。
async function postData() {
const url = 'https://jsonplaceholder.typicode.com/posts';
const data = { title: 'foo', body: 'bar', userId: 1 };
try {
const response = await fetch(url, {
method: 'POST', // 指定方法
headers: {
'Content-Type': 'application/json' // 声明发送的数据类型
},
body: JSON.stringify(data) // 将对象转换为 JSON 字符串
});
if (!response.ok) {
throw new Error(`HTTP error! status: ${response.status}`);
}
const responseData = await response.json();
console.log('服务器返回:', responseData);
} catch (error) {
console.error('发送失败:', error);
}
}
postData();
暂无评论,快来抢沙发吧!