发起HTTP请求是前端开发中绕不开的话题,无论是获取列表数据、提交表单还是上传文件,底层都依赖HTTP通信。目前主流方案有两个:浏览器原生的fetch API和第三方库axios。两者都能完成任务,但在细节设计上差异不小,选错了方案往往会让代码变得冗长难维护。本文会先把两个API的基本用法讲清楚,再从多个实际维度做对比,最后给出选型建议。

一、fetch和axios的基本用法
fetch是浏览器内置的函数,属于原生JavaScript API,不需要安装任何依赖,在支持的环境里可以直接调用。它基于Promise设计,调用后返回一个包含Response对象的Promise。一个最简单的GET请求如下:
// 基本的fetch GET请求
fetch('https://api.ipipp.com/users')
.then(response => {
// fetch不会自动解析JSON,需要手动调用json方法
if (!response.ok) {
throw new Error('请求失败,状态码:' + response.status);
}
return response.json();
})
.then(data => console.log(data))
.catch(error => console.error('出错了', error));axios是社区广泛使用的第三方HTTP库,既可以运行在浏览器中,也可以在Node.js环境使用。它同样基于Promise,但对很多细节做了封装,比如自动转换JSON、自动拼接查询参数等。安装后使用方式如下:
// 安装:npm install axios
import axios from 'axios';
// 基本的axios GET请求
axios.get('https://api.ipipp.com/users', {
params: { page: 1, size: 10 } // 自动拼接为 ?page=1&size=10
})
.then(response => {
// axios直接把后端返回的JSON解析好放在data字段
console.log(response.data);
})
.catch(error => console.error('出错了', error));从这两段代码能直观感受到一个区别:fetch返回的Response对象把状态码、响应头、响应体分开存放,需要手动调用json()方法才能拿到数据;而axios的response对象中,data属性已经是解析好的JSON数据,同时还有status、headers等信息。另外axios在传参时使用params选项,库会自动做URL编码并拼接查询字符串,用fetch就得自己拼或者借助URLSearchParams,这点在小项目里可能无所谓,但在参数较多的查询场景下差异会明显。
二、错误处理机制的重大差异
这是两个方案最容易被忽视、也最容易踩坑的区别。fetch的设计哲学是只对网络错误抛出异常,对于HTTP状态码不做判断。也就是说,当服务器返回404或者500时,fetch的Promise依然会resolve,只有断网、DNS解析失败这类网络层错误才会reject。
// 错误示范:状态码500时这段代码不会走catch
fetch('https://api.ipipp.com/orders/999')
.then(res => res.json())
.then(data => {
// 即使返回500,这里依然会执行,data可能是错误页面的HTML
console.log(data);
})
.catch(err => console.log('只有网络错误才会到这里'));如果不做处理,后端报错时页面可能表现出“请求成功但数据异常”的诡异现象,排查起来非常费劲。正确做法是检查response.ok属性(状态码在200到299之间为true),或者手动判断response.status,不满足条件就主动抛出异常。
axios在这方面的处理更符合直觉:只要状态码不在默认的2xx范围内,就会自动reject,直接进入catch分支。而且axios的错误对象信息更丰富,error.response里有完整的响应数据,error.request是请求配置,方便统一记录日志。再加上axios支持配置validateStatus来自定义哪些状态码算成功,灵活性也不差。
三、超时控制、拦截器与取消请求
fetch原生不支持timeout配置,实现超时需要借助AbortController配合setTimeout手动包装,写法略显繁琐:
// fetch实现超时控制
const controller = new AbortController();
const timer = setTimeout(() => controller.abort(), 5000); // 5秒超时
fetch('https://api.ipipp.com/slow-api', { signal: controller.signal })
.then(res => res.json())
.then(data => console.log(data))
.catch(err => {
if (err.name === 'AbortError') {
console.log('请求超时被取消');
}
})
.finally(() => clearTimeout(timer));axios则只需要一个timeout配置项,单位毫秒,超时后会抛出ECONNABORTED错误,代码量几乎为零。此外,axios的拦截器是很多人选择它的核心原因。请求拦截器可以在每个请求发出前统一附加token、修改头部;响应拦截器可以统一处理错误码、跳转登录页、弹出提示,业务代码里就不用到处写重复逻辑:
// axios拦截器统一处理token和错误
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.data, // 直接返回数据,调用处更简洁
err => {
if (err.response && err.response.status === 401) {
// token过期,跳转登录页
location.href = '/login';
}
return Promise.reject(err);
}
);在取消请求方面,新版axios同样支持AbortController,旧版本使用CancelToken,两者都能满足搜索联想词防抖、路由切换时取消旧请求这类场景。fetch虽然也通过AbortController实现了取消能力,但缺少并发控制方法,axios自带的axios.all和axios.spread(新版本推荐直接用Promise.all)在批量请求场景下依然更顺手一些。
四、如何根据项目情况做选择
如果项目对包体积极其敏感,比如要做移动端H5落地页、浏览器扩展,或者只是简单拉取几条数据,fetch是更好的选择。它是原生的,零依赖,配合URLSearchParams和AbortController基本能覆盖常见需求,现代浏览器的支持也已经很完善。另外在Node.js 18以上版本中,fetch已经内置,写脚本调接口时甚至不需要安装任何库。
如果是中大型业务项目,需要统一的请求封装、拦截器、自动错误处理、上传进度监听(axios的onUploadProgress在文件上传场景很实用),那么axios带来的开发效率提升远超十几KB的体积代价。团队协作时,统一的拦截器约定也能降低维护成本,新人接手项目时不需要在每个请求里翻找token逻辑。
还有一种务实做法:两者结合使用。框架无关的场景用fetch写轻量请求,业务主链路用axios封装统一入口。选型的核心不是哪个“更先进”,而是哪个更贴合项目规模、团队习惯和长期维护成本。理解了fetch在错误处理和响应结构上的特殊性之后,无论选哪个,都能写出健壮的请求代码。