Fetch 能用了,为什么大家还在装 axios?

一、先搞清楚:浏览器到底在干什么

你打开一个网页,发生了什么?

你的浏览器向一台远程服务器说:"给我那个页面。" 服务器把 HTML 扔回来。你点了张图片,浏览器又说:"给我那张图。" 服务器把图片字节扔过来。你提交了表单,浏览器说:"这是用户填的数据,存一下。" 服务器说:"存好了。"

整个过程就是一件事:发请求,等回应。

这就像你往邮局投信——你写好地址和内容,投进信箱,然后等回信。只不过这里邮递员是光速的,信箱是 TCP 连接,而"信"的格式是 HTTP 协议。

从 1991 年开始,浏览器就在干这件事。问题是:浏览器用什么"语言"让 JavaScript 去发这个请求?


二、前任:XMLHttpRequest(XHR)

2000 年左右,微软发明了一个东西叫 XMLHttpRequest。名字里带着"XML",但实际上什么格式都能传——JSON、文本、二进制都行。Google 用它在 Gmail 里实现了"无刷新更新",从此 Ajax 这个词火了。

它长这样:

// 经典 XHR 写法
var xhr = new XMLHttpRequest();
xhr.open('GET', '/api/users', true);
xhr.onreadystatechange = function() {
  if (xhr.readyState === 4) {          // 4 = 请求完成
    if (xhr.status === 200) {           // 200 = 成功
      var users = JSON.parse(xhr.responseText);
      console.log(users);
    } else {
      console.log('出错了: ' + xhr.status);
    }
  }
};
xhr.send();

你看这段代码——你得先 new 一个对象,然后 open,然后监听 onreadystatechange,然后在回调里判断 readyState === 4status === 200,最后 send

这东西的设计哲学是事件驱动的:状态从 0 变到 1,1 变到 2,2 变到 3,3 变到 4。你像在监听一台机器的仪表盘,每次指针动了你得自己判断"现在该干嘛了"。

它管用吗?管用。它丑吗?丑得让人想骂人。

问题在于:HTTP 请求本质上是一个异步操作——你发出去,等一会儿,结果回来。这是 Promise 的天然场景。但 XHR 诞生于 Promise 出现之前,所以它用了回调函数和状态机来表达这件事。这不是 XHR 的错,是时代的局限。


三、Fetch 登场:用 Promise 重新表达同一件事

2015 年,Fetch API 被正式纳入浏览器标准。核心思想一句话:

HTTP 请求是异步操作 → 用 Promise 表达它。

fetch('/api/users')
  .then(res => res.json())
  .then(users => console.log(users))
  .catch(err => console.error(err));

一个函数调用,返回一个 Promise。你链式处理:先拿到响应对象,再把它解析成 JSON,然后处理数据。没有状态码要记,没有回调要嵌套。

async/await 写更直观:

async function getUsers() {
  try {
    const res = await fetch('/api/users');
    const users = await res.json();
    console.log(users);
  } catch (err) {
    console.error(err);
  }
}

读起来就像同步代码——"等一下请求结果,等一下 JSON 解析,打印出来"。这就是 Fetch 的魅力。


四、实际用法:从简单到复杂

案例 1:GET 请求——获取数据

最简单的场景:从服务器拿一个用户列表。

async function fetchUsers() {
  const res = await fetch('https://api.example.com/users');
  const users = await res.json();
  console.log(users);
  // [{ id: 1, name: '前端王睿' }, { id: 2, name: '爱因斯坦' }, ...]
}

案例 2:带查询参数

URL 里拼参数就行,但手动拼容易出错:

// 不推荐:手动拼接
fetch('/api/users?name=前端王睿&age=18')

// 推荐:用 URLSearchParams
const params = new URLSearchParams({
  name: '前端王睿',
  age: 18
});
const res = await fetch(`/api/users?${params}`);

案例 3:POST 请求——发送 JSON 数据

async function createUser() {
  const res = await fetch('/api/users', {
    method: 'POST',
    headers: {
      'Content-Type': 'application/json'
    },
    body: JSON.stringify({
      name: '前端王睿',
      age: 18,
      title: '前端开发工程师'
    })
  });
  const data = await res.json();
  console.log('创建成功:', data);
}

案例 4:上传文件

async function uploadFile(fileInput) {
  const formData = new FormData();
  formData.append('avatar', fileInput.files[0]);
  formData.append('userId', '42');

  const res = await fetch('/api/upload', {
    method: 'POST',
    body: formData   // 不要手动设 Content-Type!浏览器会自动设
  });
  console.log('上传成功:', await res.json());
}

注意:上传文件时千万不要手动设 Content-Type: multipart/form-data。浏览器需要自动生成 boundary 分隔符,你手动设了反而会出错。这是 Fetch 的一个"坑"——它不会帮你纠正这个错误。

案例 5:带认证 Token

const token = localStorage.getItem('auth_token');

const res = await fetch('/api/profile', {
  headers: {
    'Authorization': `Bearer ${token}`,
    'Content-Type': 'application/json'
  }
});

案例 6:设置超时(Fetch 原生没有!)

Fetch 没有内置超时功能。如果服务器卡住了,你的请求会永远等下去。你得用 AbortController

async function fetchWithTimeout(url, timeout = 5000) {
  const controller = new AbortController();
  const timeoutId = setTimeout(() => controller.abort(), timeout);

  try {
    const res = await fetch(url, { signal: controller.signal });
    return await res.json();
  } catch (err) {
    if (err.name === 'AbortError') {
      throw new Error(`请求超时(${timeout}ms)`);
    }
    throw err;
  } finally {
    clearTimeout(timeoutId);
  }
}

// 使用
const data = await fetchWithTimeout('/api/slow-endpoint', 5000);

案例 7:处理 HTTP 错误状态码

这是 Fetch 最让人困惑的设计——404、500 不会触发 catch!

// ❌ 你以为这样能捕获 404 错误,实际上不能
try {
  const res = await fetch('/api/missing');   // 服务器返回 404
  const data = await res.json();
  console.log(data);
} catch (err) {
  console.log('出错了');  // 这里不会执行!
}

Fetch 认为:请求发出去了,服务器也回应了——"请求成功了"。至于服务器回应的内容是"404 Not Found",那是业务层面的成功,不是网络层面的失败。

所以你必须自己检查 res.ok

// ✅ 正确做法
async function safeFetch(url) {
  const res = await fetch(url);

  if (!res.ok) {
    throw new Error(`HTTP ${res.status}: ${res.statusText}`);
  }

  return res.json();
}

try {
  const data = await safeFetch('/api/users');
  console.log(data);
} catch (err) {
  console.error(err.message);  // "HTTP 404: Not Found"
}

这就像你问邮递员信送到了没有,他说送到了——但信的内容写着"房子着火了"。邮递员觉得他的任务完成了,因为信确实送到了。至于信的内容好不好,那不是他管的事。


五、那为什么大家还在用 axios?

好了,现在到了核心问题。Fetch 能用,为什么大家还在装 axios?

因为 Fetch 是积木,axios 是工具。 注意:axios 底层不是 Fetch,而是 XMLHttpRequest。两者是各自独立的封装路线,但 axios 解决的是同一类问题——原生 API 太简陋,开发者需要更多开箱即用的功能。

Fetch 的设计哲学是:我只提供最基础的 HTTP 操作能力。超时、拦截器、自动 JSON 转换、错误处理——这些"实际开发中你每天都要做的事",Fetch 不管。它管不管?不管。你自己写。

axios 把这些事情全帮你做了。让我逐个对比:

差异 1:错误处理

Fetch(需要手动检查):

const res = await fetch('/api/users');
if (!res.ok) throw new Error(`HTTP ${res.status}`);
const data = await res.json();

axios(自动处理):

const { data } = await axios.get('/api/users');
// 状态码 >= 400 自动 throw,直接进 catch

差异 2:JSON 转换

Fetch(每次都要手动):

const res = await fetch(url);
const data = await res.json();   // 多一行

axios(自动解析):

const { data } = await axios.get(url);  // 直接给你数据

差异 3:超时

Fetch(15 行代码):

const controller = new AbortController();
const timeoutId = setTimeout(() => controller.abort(), 5000);
try {
  const res = await fetch(url, { signal: controller.signal });
  // ...
} finally {
  clearTimeout(timeoutId);
}

axios(一行):

const data = await axios.get(url, { timeout: 5000 });

差异 4:拦截器——真正的杀手锏

假设你有 50 个 API 调用,每个都需要带 token,每个都需要统一错误处理。

用 Fetch,你得自己封装一个 wrapper,或者在每个调用处重复写:

// 每个 API 文件都得这样写
const token = localStorage.getItem('token');
const res = await fetch(url, {
  headers: { 'Authorization': `Bearer ${token}` }
});
if (!res.ok) { /* 处理错误 */ }

用 axios,你只需配置一次拦截器:

// 配置一次,全局生效
axios.interceptors.request.use(config => {
  const token = localStorage.getItem('token');
  if (token) {
    config.headers.Authorization = `Bearer ${token}`;
  }
  return config;
});

axios.interceptors.response.use(
  res => res,
  err => {
    if (err.response.status === 401) {
      // token 过期,自动跳转登录
      window.location.href = '/login';
    }
    return Promise.reject(err);
  }
);

之后你写的每一个 axios.get()axios.post() 都自动带 token,自动处理 401。写一次,管全局。

差异 5:请求/响应进度

上传大文件时,你想显示进度条。

Fetch:几乎做不到(技术上可以用 ReadableStream,但极其复杂)。

axios

await axios.post('/upload', formData, {
  onUploadProgress: (event) => {
    const percent = Math.round((event.loaded / event.total) * 100);
    console.log(`上传进度: ${percent}%`);
  }
});

六、一张表总结全部差异

特性 Fetch axios
来源 浏览器原生 第三方库(~13KB)
底层 Promise Promise
JSON 自动解析 不自动,需 res.json() 自动
HTTP 错误状态码 不 throw,需手动检查 res.ok 自动 throw
超时 需手动用 AbortController { timeout: 5000 } 一行搞定
请求拦截器 没有
响应拦截器 没有
上传/下载进度 极难实现 内置 onUploadProgress
取消请求 AbortController(现代浏览器) 内置
Node.js 支持 Node 18+ 原生支持 通用
学习成本 低(基础简单,进阶复杂)

七、实际场景:你应该用哪个?

我不会给你一个简单的答案——因为真正的答案取决于你在做什么

场景 A:个人小项目 / 脚本

你写一个 Chrome 扩展,或者一个只有两三个 API 调用的小页面。

用 Fetch。 没必要为了两三个请求引入一个库。写个薄薄的 wrapper 就够了:

async function api(url, options = {}) {
  const res = await fetch(url, {
    headers: { 'Content-Type': 'application/json', ...options.headers },
    ...options
  });
  if (!res.ok) throw new Error(`HTTP ${res.status}`);
  return res.json();
}

20 行代码,覆盖 80% 的场景。

场景 B:正经的前端应用

你在做一个有几十个 API 调用的应用,需要统一加 token、统一错误处理、统一超时。

用 axios。 或者用基于 Fetch 封装的库(如 kywretchofetch)。

核心不是 Fetch 不好,而是你不想在每个 API 文件里都重复写那 5 行样板代码。拦截器帮你把这些重复逻辑集中到一处。

场景 C:需要上传进度条 / 大文件传输

用 axios。 Fetch 在进度回调方面几乎无能为力。

场景 D:包体积极其敏感

你在做移动端 H5,每个 KB 都要算。或者你在做一个 SDK。

用 Fetch。 axios.min.js 约 13KB,Fetch 是 0KB。13KB 在移动端可能意味着 200ms 的加载延迟。

场景 E:Node.js 服务端

你写一个同时跑在浏览器和 Node.js 的同构应用。

用 axios。 虽然 Node 18+ 原生支持 Fetch,但 axios 的 API 在两个环境完全一致,而 Fetch 在 Node 里没有 window,可能有一些细微差异。


八、如果你决定用 Fetch,这是一个能用的封装

class HttpClient {
  constructor(baseURL = '', defaultOptions = {}) {
    this.baseURL = baseURL;
    this.defaultOptions = defaultOptions;
  }

  async request(url, options = {}) {
    const finalUrl = url.startsWith('http') ? url : `${this.baseURL}${url}`;
    const finalOptions = {
      ...this.defaultOptions,
      ...options,
      headers: {
        'Content-Type': 'application/json',
        ...this.defaultOptions.headers,
        ...options.headers
      }
    };

    // 超时处理
    if (finalOptions.timeout) {
      const controller = new AbortController();
      const timeoutId = setTimeout(() => controller.abort(), finalOptions.timeout);
      finalOptions.signal = controller.signal;
      delete finalOptions.timeout;

      try {
        return await this._doRequest(finalUrl, finalOptions);
      } finally {
        clearTimeout(timeoutId);
      }
    }

    return this._doRequest(finalUrl, finalOptions);
  }

  async _doRequest(url, options) {
    const res = await fetch(url, options);

    if (!res.ok) {
      const errorBody = await res.text().catch(() => '');
      throw new Error(`HTTP ${res.status}: ${res.statusText}\n${errorBody}`);
    }

    const contentType = res.headers.get('content-type');
    if (contentType && contentType.includes('application/json')) {
      return res.json();
    }
    return res.text();
  }

  get(url, options) {
    return this.request(url, { ...options, method: 'GET' });
  }

  post(url, data, options) {
    return this.request(url, {
      ...options,
      method: 'POST',
      body: JSON.stringify(data)
    });
  }

  put(url, data, options) {
    return this.request(url, {
      ...options,
      method: 'PUT',
      body: JSON.stringify(data)
    });
  }

  delete(url, options) {
    return this.request(url, { ...options, method: 'DELETE' });
  }
}

用法:

const api = new HttpClient('https://api.example.com', {
  headers: { 'Authorization': 'Bearer my-token' }
});

// GET
const users = await api.get('/users');

// POST
const newUser = await api.post('/users', { name: '前端王睿' });

// 带超时
const data = await api.get('/slow-endpoint', { timeout: 5000 });

九、总结

Fetch 是浏览器给你的原生能力。它简单、轻量、无处不在。

axios 是社区基于实际痛点做的封装。它底层用 XMLHttpRequest,补上了原生 API 没帮你做的事——错误处理、超时、拦截器、自动 JSON 转换。这些功能 Fetch 也没有,XHR 也没有,axios 把它们全包了。

理解 Fetch,你才理解 axios 在帮你做什么。 不理解 Fetch 直接用 axios,那你只是在用你不懂的工具。

从第一性原理出发:HTTP 请求是"发请求、等回应"。Fetch 用 Promise 表达了这件事。axios 则在 XMLHttpRequest 的基础上做了封装,补上了实际开发中每天都要用的功能——错误处理、超时、拦截器、自动 JSON 转换。

这不是谁替代谁的问题。这是不同层次的抽象

如果你连 Fetch 的底层行为都没搞清楚就用了 axios,你就是在骗自己。反之,如果你理解了 Fetch 还选择用 axios,那是你做了一个明智的决定。

最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
【社区内容提示】社区部分内容疑似由AI辅助生成,浏览时请结合常识与多方信息审慎甄别。
平台声明:文章内容(如有图片或视频亦包括在内)由作者上传并发布,文章内容仅代表作者本人观点,简书系信息发布平台,仅提供信息存储服务。

友情链接更多精彩内容