Ajax 与 Fetch API:前后端数据交互
Ajax 与 Fetch API:前后端数据交互
在现代 Web 开发中,页面需要在不刷新整个页面的情况下与服务器交换数据并更新部分内容,这正是 Ajax(Asynchronous JavaScript and XML)的核心能力。而 Fetch API 作为原生 JavaScript 提供的现代化替代方案,以更简洁的 Promise 语法让异步网络请求变得更加直观。本教程将带你从基础概念出发,深入掌握 Ajax 与 Fetch API 的使用。
什么是 Ajax?
Ajax 并非一门单一的技术,而是一组技术的集合,通常包括:
- HTML/CSS 用于呈现内容
- DOM 动态操作页面
- XMLHttpRequest 对象(与服务器异步通信)
- 通常使用 JSON 作为数据格式(尽管名称中包含 XML)
Ajax 的核心思想是:在后台发送 HTTP 请求,获取数据后只更新页面的一部分,而不是重新加载整个页面。 这极大地提升了用户体验,使 Web 应用更接近桌面应用的流畅感。
XMLHttpRequest 基础
在 Fetch API 出现之前,XMLHttpRequest(XHR)是发送 Ajax 请求的唯一方式。虽然如今 Fetch 更常用,但理解 XHR 仍然重要,因为你可能会在维护旧代码时遇到它。
创建 XHR 对象
const xhr = new XMLHttpRequest();
配置请求
使用 open 方法设置请求方法和 URL:
xhr.open('GET', 'https://api.example.com/data');
第三个参数默认为 true,表示异步执行。
发送请求
xhr.send();
如果是 POST 请求,需要先设置请求头,并在 send 方法中传入请求体:
xhr.open('POST', 'https://api.example.com/submit');
xhr.setRequestHeader('Content-Type', 'application/json');
xhr.send(JSON.stringify({ name: 'Alice' }));
监听响应
通过 onreadystatechange 事件或 onload 事件处理响应。
xhr.onreadystatechange = function() {
if (xhr.readyState === 4 && xhr.status === 200) {
const data = JSON.parse(xhr.responseText);
console.log(data);
}
};
readyState 的几个关键值:0: 未初始化,1: 已调用 open,2: 已发送,3: 正在接收,4: 完成。现代开发中更常用 onload,它只在请求成功完成时触发。
xhr.onload = function() {
if (xhr.status >= 200 && xhr.status < 300) {
console.log('成功', xhr.response);
} else {
console.error('请求失败', xhr.status);
}
};
xhr.onerror = function() {
console.error('网络错误');
};
XHR 的局限
- 基于回调的异步模式容易导致“回调地狱”
- 需要手动解析 JSON
- 错误处理不够直观,需同时处理
onerror和状态码 - 不支持
Promise,与现代异步代码结合困难
正因为这些局限,Fetch API 应运而生。
Fetch API:现代网络请求
fetch() 是全局函数,返回一个 Promise,表示异步请求的完成或失败。它的基本语法非常简单:
fetch(url, options)
.then(response => response.json())
.then(data => console.log(data))
.catch(error => console.error('错误:', error));
发送 GET 请求
fetch('https://api.example.com/posts')
.then(response => {
if (!response.ok) {
throw new Error(`HTTP error! status: ${response.status}`);
}
return response.json();
})
.then(posts => {
// 更新 DOM
displayPosts(posts);
})
.catch(error => {
console.error('获取数据失败:', error);
});
重要提示: fetch 只在网络错误(比如断网)时才会 reject。如果服务器返回 404 或 500 状态码,Promise 仍会 resolve,你需要通过 response.ok 或 response.status 来判断请求是否成功。
发送 POST 请求
通过第二个参数的 method、headers 和 body 来控制请求。
fetch('https://api.example.com/posts', {
method: 'POST',
headers: {
'Content-Type': 'application/json',
},
body: JSON.stringify({
title: '新文章',
content: '这是文章内容',
author: 'Alice'
})
})
.then(response => response.json())
.then(data => console.log('创建成功:', data))
.catch(error => console.error('创建失败:', error));
使用 async/await
更简洁的写法是配合 async/await:
async function fetchPosts() {
try {
const response = await fetch('https://api.example.com/posts');
if (!response.ok) {
throw new Error(`请求失败,状态码: ${response.status}`);
}
const data = await response.json();
console.log(data);
} catch (error) {
console.error('发生错误:', error);
}
}
fetchPosts();
Fetch API 常用选项
Headers 自定义
创建 Headers 对象或直接传入对象字面量来设置请求头。
const headers = new Headers({
'Authorization': 'Bearer your-token',
'Accept': 'application/json'
});
fetch(url, { headers });
处理不同响应类型
response.json()– 解析为 JSONresponse.text()– 读取为文本response.blob()– 处理二进制数据(如图片)response.formData()– 读取 FormDataresponse.arrayBuffer()– 获取原始二进制缓冲区
示例:获取一张图片并展示
fetch('https://example.com/image.png')
.then(response => response.blob())
.then(blob => {
const imageUrl = URL.createObjectURL(blob);
document.querySelector('img').src = imageUrl;
});
控制请求模式
mode 选项决定是否允许跨域请求:
'cors'– 允许跨域(默认)'no-cors'– 只允许简单请求,无法读取响应'same-origin'– 仅同源请求
fetch('https://other-domain.com/data', {
mode: 'cors',
credentials: 'include' // 携带 cookie
});
取消请求:AbortController
有时需要取消正在进行的 fetch 请求,比如用户离开页面或输入新的搜索词。
const controller = new AbortController();
const signal = controller.signal;
fetch(url, { signal })
.then(response => response.json())
.then(data => console.log(data))
.catch(err => {
if (err.name === 'AbortError') {
console.log('请求被取消');
} else {
console.error('其他错误', err);
}
});
// 取消请求
controller.abort();
Ajax 与 Fetch 的对比
| 特性 | XMLHttpRequest | Fetch API |
|---|---|---|
| 语法 | 回调函数 | Promise (支持 async/await) |
| 错误处理 | 需要分别处理 onerror 和状态码 | 统一通过 catch(但需手动检查 response.ok) |
| 跨域 | 通过 withCredentials 和事件 | 通过 credentials 选项控制 |
| 取消请求 | xhr.abort() | AbortController |
| 进度事件 | 支持 (upload/download) | 暂无原生支持 |
| 流式处理 | 有限支持 | ReadableStream 支持流式读取 |
| 简洁性 | 较繁琐 | 简洁优雅 |
尽管 Fetch API 更现代,但在需要上传进度监控或取消请求的同时重置连接等场景下,XHR 仍然有其用武之地。不过绝大多数日常开发中,Fetch 是更好的选择。
常见实战场景
动态加载列表
async function loadUserList() {
const container = document.getElementById('user-list');
container.innerHTML = '加载中...';
try {
const res = await fetch('/api/users');
if (!res.ok) throw new Error('列表加载失败');
const users = await res.json();
container.innerHTML = users.map(user => `<li>${user.name}</li>`).join('');
} catch (e) {
container.innerHTML = '加载失败,请稍后重试';
}
}
表单提交与错误提示
document.getElementById('login-form').addEventListener('submit', async (e) => {
e.preventDefault();
const formData = new FormData(e.target);
const submitBtn = e.target.querySelector('button');
submitBtn.disabled = true;
submitBtn.textContent = '提交中...';
try {
const res = await fetch('/api/login', {
method: 'POST',
body: formData // 可直接传 FormData,无需设置 Content-Type
});
const result = await res.json();
if (!res.ok) {
showError(result.message || '登录失败');
} else {
window.location.href = '/dashboard';
}
} catch {
showError('网络异常,请检查连接');
} finally {
submitBtn.disabled = false;
submitBtn.textContent = '登录';
}
});
带 token 的授权请求
async function fetchProtectedData() {
const token = localStorage.getItem('token');
const res = await fetch('/api/protected', {
headers: {
'Authorization': `Bearer ${token}`
}
});
if (res.status === 401) {
// token 失效,重定向到登录
window.location.href = '/login';
return;
}
const data = await res.json();
renderData(data);
}
并行请求与错误处理
当需要同时请求多个独立资源时,可以使用 Promise.all 提高加载效率。
async function loadDashboard() {
try {
const [userRes, statsRes] = await Promise.all([
fetch('/api/user'),
fetch('/api/stats')
]);
if (!userRes.ok || !statsRes.ok) {
throw new Error('部分请求失败');
}
const user = await userRes.json();
const stats = await statsRes.json();
buildDashboard(user, stats);
} catch (e) {
console.error('面板加载失败:', e);
}
}
如果要让部分请求即使失败也不影响其他请求,可以使用 Promise.allSettled。
常见问题与最佳实践
-
忘记检查
response.ok:很多人认为fetch返回非 2xx 状态码时会进入catch,实际上它只会reject网络错误。务必在处理响应时先检查ok或状态码。 -
未处理跨域:API 必须支持 CORS,否则浏览器会阻止请求。后端需要设置正确的
Access-Control-Allow-Origin头。如果只是开发环境临时解决,可以使用代理。 -
请求重复提交:在用户快速点击按钮时,可能发起多次 POST 请求。可以在请求前禁用按钮,或使用防抖/节流,或利用 AbortController 取消前一个请求。
-
安全注意事项:
- 始终验证和清理用户输入,但这是后端工作
- 不要在客户端存储敏感信息
- 使用 HTTPS 传输数据
- 防范 CSRF:配合 token 或 SameSite Cookie
-
性能优化:
- 缓存 GET 请求结果,利用
cache选项('default','no-store','reload','force-cache') - 避免在循环中频繁调用 fetch
- 考虑使用懒加载或分页减少单次数据量
- 缓存 GET 请求结果,利用
-
错误日志与用户体验:提供友好的错误提示,不要直接将原始错误信息暴露给用户;在控制台记录详细错误便于调试。
总结
Ajax 概念彻底改变了 Web 的交互方式,而 Fetch API 将这种异步通信带入了现代 Promise 世界。掌握它们意味着你可以构建丰富、响应迅速的单页应用和无刷新数据交互功能。从今天起,尝试在项目中使用 fetch + async/await 替换旧的 XHR,你会感受到代码可读性和可维护性的显著提升。
若想进一步探索,可以查看 MDN Fetch API 文档 获取更详细的配置选项和用法示例。