嘿,朋友。咱们今天不聊虚的,就聊聊前端开发里最基础、但也最让人“爱恨交织”的两个家伙——Fetch 和 Axios。
你是不是也曾这样:明明代码没报错,数据就是拿不到?或者 POST 请求发出去,后端收到的却是空对象?又或者是想拦截请求加个 Token,却发现 fetch 简直像是在徒手挖隧道,而 axios 早就给你修好了高速公路?
别急,我把这些年踩过的坑、掉过的头发,都整理成了这篇干货。无论你是刚入门的新手,还是想重构代码的老手,这篇都能帮你把GET、POST、PUT、DELETE 的请求姿势彻底搞明白。
一、先说结论:我们为什么还在争论这两个?
在 2024 年,浏览器原生支持了 fetch API,理论上,大家应该都在用原生方案了吧?
现实是: 生产环境中,超过 70% 的 React/Vue 项目依然在使用 Axios。
为什么?因为 Fetch 太“原始”了。它就像一辆只给仪表盘和方向盘的赛车,动力强劲,但没ABS、没气囊、没导航。而 Axios 是辆精装版的特斯拉,内置了拦截器、自动转换 JSON、超时控制等“豪华配置”。
但作为开发者,你必须懂 Fetch,因为它是基石,而且在某些轻量级场景(如 Next.js Server Components)中,它是唯一选择。
二、核心区别:不止是语法不同
| 特性 | fetch (原生) |
axios (第三方库) |
|---|---|---|
| 底层实现 | 基于 Promise 的原生 API | 基于 XMLHttpRequest + Promise |
| JSON 处理 | 手动 res.json() |
自动 序列化和反序列化 |
| 错误判断 | 仅网络错误才 reject,4xx/5xx 也是 resolve | 所有 HTTP 状态码非 2xx 均 reject |
| 请求拦截 | 无原生支持,需封装 | 内置 interceptors,极其强大 |
| 响应拦截 | 无 | 内置,可做统一权限处理 |
| 取消请求 | AbortController (较新) |
CancelToken 或 AbortController |
| 浏览器兼容 | IE 不支持 | 支持 IE9+ (axios@0.19.x) |
| 体积 | 0kb (原生) | ~13kb (gzip 后) |
关键点:
fetch的“坑”在于,当你请求一个 404 页面时,fetch不会报错!它会成功返回一个状态为 404 的 Response 对象。这会让很多初学者以为请求成功了,直到后续代码崩溃。
三、起手式:环境准备
假设我们的后端接口地址是 https://api.example.com。
我们将使用以下模拟数据结构:
- 用户列表:
GET /users - 创建用户:
POST /users - 更新用户:
PUT /users/1 - 删除用户:
DELETE /users/1
四、GET 请求:获取数据
1. Fetch 写法
async function getUsersFetch() {
try {
// fetch 默认是 GET
const response = await fetch('https://api.example.com/users');
// 【重要坑点】fetch 即使 404/500 也会进入 then,必须手动检查 status
if (!response.ok) {
throw new Error(`HTTP error! status: ${response.status}`);
}
// 必须手动调用 .json() 解析
const data = await response.json();
console.log('Fetch 获取数据:', data);
return data;
} catch (error) {
console.error('Fetch 请求失败:', error.message);
}
}
2. Axios 写法
async function getUsersAxios() {
try {
// axios 自动处理 JSON,且 4xx/5xx 会自动进入 catch
const res = await axios.get('https://api.example.com/users');
// axios 的响应数据在 res.data 里,而不是 res 本身
console.log('Axios 获取数据:', res.data);
return res.data;
} catch (error) {
// error.response 包含服务端返回的状态码和错误信息
if (error.response) {
console.error('Axios 请求失败:', error.response.status);
} else {
console.error('Axios 网络错误:', error.message);
}
}
}
对比感悟:
看,Axios 的代码是不是少了一堆 if (!response.ok) 的判断?而且 res.data 直接就是我们要的数据对象,不用再去 .json() 了。这就是 Axios 节省时间的地方。
五、POST 请求:提交数据
POST 是最容易出错的,尤其是 Content-Type 设置。
场景:提交一个用户注册信息
const userData = {
username: 'zhangsan',
password: '123456',
email: 'zhangsan@example.com'
};
1. Fetch 写法(容易踩坑)
async function createUserFetch() {
try {
const response = await fetch('https://api.example.com/users', {
method: 'POST',
headers: {
'Content-Type': 'application/json', // 【必须】显式声明,否则后端可能收不到 JSON
'Authorization': 'Bearer my-token' // 比如 Token 也在这里加
},
body: JSON.stringify(userData) // 【必须】手动序列化
});
if (!response.ok) {
throw new Error(`POST failed: ${response.status}`);
}
const data = await response.json();
console.log('创建成功:', data);
return data;
} catch (error) {
console.error('创建失败:', error);
}
}
为什么容易错?
如果你忘了 JSON.stringify(),body 会变成 [object Object],后端收不到数据。如果你忘了 Content-Type,很多后端框架默认按表单解析,也会报错。
2. Axios 写法(优雅很多)
async function createUserAxios() {
try {
// axios 自动序列化为 JSON,自动设置 Content-Type: application/json
// 第二个参数是 data(请求体),第三个参数是 config
const res = await axios.post('https://api.example.com/users', userData, {
headers: {
'Authorization': 'Bearer my-token'
}
});
console.log('创建成功:', res.data);
return res.data;
} catch (error) {
console.error('创建失败:', error.response?.data || error.message);
}
}
对比感悟:
Axios 里,你直接传对象 userData,它会自己搞定序列化和头信息。这减少了 80% 的“为什么后端收不到数据”的 debug 时间。
六、PUT 和 DELETE:更新与删除
这两个方法在 Fetch 和 Axios 中逻辑类似,但细节仍有差异。
1. PUT 请求(更新用户信息)
async function updateUserFetch(id, updateData) {
const res = await fetch(`https://api.example.com/users/${id}`, {
method: 'PUT',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify(updateData)
});
return res.json();
}
async function updateUserAxios(id, updateData) {
// axios.put(url, data)
const res = await axios.put(`https://api.example.com/users/${id}`, updateData);
return res.data;
}
2. DELETE 请求(删除用户)
注意: DELETE 请求通常没有请求体(Body),但有些后端框架允许带 body(如 RESTful 扩展)。
async function deleteUserFetch(id) {
const res = await fetch(`https://api.example.com/users/${id}`, {
method: 'DELETE'
// 注意:fetch DELETE 通常不需要 body,如果传了 body 可能被忽略
});
return res; // 有时候删除接口返回 204 No Content,没有 body
}
async function deleteUserAxios(id) {
const res = await axios.delete(`https://api.example.com/users/${id}`);
return res;
}
常见错误:
在 Fetch 中,如果你写 fetch(url, { method: 'DELETE', body: JSON.stringify({id}) }),Chrome 控制台可能会警告:“Request body is not allowed for DELETE request”。虽然大多数浏览器会忽略这个警告并发送请求,但最好遵循规范。
七、高阶功能:Axios 的杀手锏 —— 拦截器
这是 Axios 能长期占据主流的根本原因。
场景:所有请求自动带上 Token,所有 401 自动跳转登录
1. 请求拦截器(Request Interceptor)
axios.interceptors.request.use(
config => {
// 每次请求前,自动从 localStorage 取 Token 并注入 header
const token = localStorage.getItem('token');
if (token) {
config.headers.Authorization = `Bearer ${token}`;
}
// 也可以在这里添加 loading 状态
console.log('即将发起请求:', config.url);
return config;
},
error => {
return Promise.reject(error);
}
);
2. 响应拦截器(Response Interceptor)
axios.interceptors.response.use(
response => {
// 统一处理成功响应
return response.data; // 直接返回 data,后续代码不用再 .data 了
},
error => {
// 统一处理错误
if (error.response) {
switch (error.response.status) {
case 401:
// Token 过期,跳转登录
window.location.href = '/login';
break;
case 403:
alert('无权访问');
break;
case 404:
alert('资源不存在');
break;
default:
alert('服务器错误');
}
} else {
alert('网络异常,请检查连接');
}
return Promise.reject(error);
}
);
Fetch 能做到吗? 能,但你需要封装一个全局函数或者用 Context 来模拟,代码量至少是 Axios 的 3-5 倍。
八、常见错误处理:避坑指南
错误 1:Fetch 把 404 当成成功
// ❌ 错误示范
fetch('/api/users/999')
.then(res => res.json()) // 这里不会报错,但 res.status 是 404
.then(data => console.log(data))
// ✅ 正确示范:手动检查状态
fetch('/api/users/999')
.then(res => {
if (!res.ok) throw new Error('Not Found');
return res.json();
})
.catch(err => console.error(err))
错误 2:axios 忘记解包 data
// ❌ 错误示范:打印出来是 {"data": {...}, "status": 200...}
console.log(res);
// ✅ 正确示范:取 res.data
console.log(res.data);
错误 3:跨域问题(CORS)
这不是代码问题,是服务器配置问题。但你可以知道:
- Fetch 和 Axios 都会触发预检请求(Preflight)如果请求头包含自定义头(如
Authorization)。 - 确保后端配置了
Access-Control-Allow-Origin和Access-Control-Allow-Headers。
错误 4:取消请求
在 SPA 应用中,用户快速点击导航,前一个请求可能还没结束。
// Axios 取消请求
let cancelToken;
function fetchData() {
// 如果有未完成的请求,先取消
if (cancelToken) {
cancelToken.cancel('用户切换了页面');
}
cancelToken = axios.CancelToken.source();
axios.get('/api/data', {
cancelToken: cancelToken.token
}).catch(thrown => {
if (axios.isCancel(thrown)) {
console.log('请求已取消');
}
});
}
// Fetch 取消请求 (使用 AbortController)
function fetchDataFetch() {
const controller = new AbortController();
const signal = controller.signal;
fetch('/api/data', { signal })
.then(res => res.json())
.catch(err => {
if (err.name === 'AbortError') {
console.log('请求已取消');
}
});
// 手动取消
// controller.abort();
}
九、2024 最新趋势:我们应该选谁?
推荐使用 Axios 的场景:
- 企业级应用:需要统一的请求拦截、错误处理、Token 刷新机制。
- 老项目维护:需要兼容 IE 浏览器。
- 复杂 HTTP 请求:如文件上传下载(Axios 处理 Blob 更简单)、进度监控。
- 团队开发:希望减少重复代码,统一封装。
推荐使用 Fetch 的场景:
- 轻量级项目:不想引入第三方依赖,追求极致打包体积。
- Next.js / Remix 等 SSR 框架:在这些框架的 Server Components 中,
fetch是首选,因为 axios 是 Node.js 库,在服务器端运行可能带来兼容性或额外配置问题。 - 简单的单页应用:只需要发几个简单的 GET 请求,不需要复杂的拦截器。
折中方案:使用 ky 或 ofetch
如果你觉得 Fetch 太原始,又觉得 Axios 太重,可以看看 Ky(基于 Fetch 的增强库)或 Ofetch(Unjs 出品,Vue/Nuxt 社区常用)。它们提供了 Fetch 的轻量 + Axios 的部分便利。
十、总结:一句话记住区别
- Fetch:原生、轻量、手动、需要你亲自检查
response.ok。 - Axios:强大、自动、拦截器、
res.data直接可用、自动处理 JSON。
给小朋友的建议: 就像搭积木,Fetch 是给你一块块原始的积木,你自己要把它们拼好;Axios 是给你一个已经装好轮子的玩具车,你只需要告诉它去哪,它自己就会跑,还能帮你挡雨(拦截错误)。
但在真正的工程世界里,了解积木(Fetch)是怎么工作的,能让你成为更好的工程师,即使你最后选择了那辆玩具车(Axios)。
希望这篇指南能帮你甩掉请求相关的 bug!如果有具体的报错,欢迎把代码贴出来,我们一起分析。
