嘿,朋友。咱们今天不聊虚的,就聊聊前端开发里最基础、但也最让人“爱恨交织”的两个家伙——FetchAxios

你是不是也曾这样:明明代码没报错,数据就是拿不到?或者 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 (较新) CancelTokenAbortController
浏览器兼容 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)

这不是代码问题,是服务器配置问题。但你可以知道:

  • FetchAxios 都会触发预检请求(Preflight)如果请求头包含自定义头(如 Authorization)。
  • 确保后端配置了 Access-Control-Allow-OriginAccess-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 的场景:

  1. 企业级应用:需要统一的请求拦截、错误处理、Token 刷新机制。
  2. 老项目维护:需要兼容 IE 浏览器。
  3. 复杂 HTTP 请求:如文件上传下载(Axios 处理 Blob 更简单)、进度监控。
  4. 团队开发:希望减少重复代码,统一封装。

推荐使用 Fetch 的场景:

  1. 轻量级项目:不想引入第三方依赖,追求极致打包体积。
  2. Next.js / Remix 等 SSR 框架:在这些框架的 Server Components 中,fetch 是首选,因为 axios 是 Node.js 库,在服务器端运行可能带来兼容性或额外配置问题。
  3. 简单的单页应用:只需要发几个简单的 GET 请求,不需要复杂的拦截器。

折中方案:使用 kyofetch

如果你觉得 Fetch 太原始,又觉得 Axios 太重,可以看看 Ky(基于 Fetch 的增强库)或 Ofetch(Unjs 出品,Vue/Nuxt 社区常用)。它们提供了 Fetch 的轻量 + Axios 的部分便利。


十、总结:一句话记住区别

  • Fetch:原生、轻量、手动、需要你亲自检查 response.ok
  • Axios:强大、自动、拦截器、res.data 直接可用、自动处理 JSON。

给小朋友的建议: 就像搭积木,Fetch 是给你一块块原始的积木,你自己要把它们拼好;Axios 是给你一个已经装好轮子的玩具车,你只需要告诉它去哪,它自己就会跑,还能帮你挡雨(拦截错误)。

但在真正的工程世界里,了解积木(Fetch)是怎么工作的,能让你成为更好的工程师,即使你最后选择了那辆玩具车(Axios)。

希望这篇指南能帮你甩掉请求相关的 bug!如果有具体的报错,欢迎把代码贴出来,我们一起分析。