原生Fetch API与axios对比:现代前端HTTP请求的最佳实践
1. 为什么我们需要重新审视原生Fetch API
最近在review一个前端项目的依赖项时,我发现axios这个"老熟人"占据了不小的体积(约12KB gzipped)。这让我开始思考:在现代浏览器几乎全面支持Fetch API的今天,我们是否真的还需要这个第三方HTTP库?
Fetch API自2015年随ES6推出以来,已经发展了近8年时间。根据CanIUse的最新数据,全球浏览器支持率已达到97.12%。即使是需要兼容旧版浏览器的项目,也可以通过简单的polyfill(仅3KB左右)实现支持。相比之下,axios虽然提供了便利的封装,但其核心功能大部分都能被原生API替代。
重要提示:如果你的项目已经深度依赖axios的拦截器、请求取消等高级特性,盲目替换可能会带来额外工作量。但对于新项目或简单请求场景,原生方案确实值得考虑。
2. Fetch API核心功能解析
2.1 基础请求示例对比
先看一个最基本的GET请求实现差异:
// axios版本 axios.get('/api/user') .then(response => console.log(response.data)) .catch(error => console.error(error)); // Fetch版本 fetch('/api/user') .then(response => { if (!response.ok) throw new Error(response.statusText); return response.json(); }) .then(data => console.log(data)) .catch(error => console.error(error));关键差异点:
- Fetch需要手动检查response.ok状态
- 响应体需要调用.json()方法解析(对于JSON数据)
- 错误处理机制不同(Fetch不会将HTTP错误状态码视为reject)
2.2 请求配置参数对照表
| 功能需求 | axios实现 | Fetch实现 |
|---|---|---|
| 请求方法指定 | method: 'post' | method: 'POST'(需大写) |
| 请求头设置 | headers: {'X-Token': 'abc'} | headers: new Headers({'X-Token': 'abc'}) |
| 请求体传参 | data: {key: 'value'} | body: JSON.stringify({key: 'value'}) |
| 超时设置 | timeout: 5000 | 需配合AbortController实现 |
| 响应类型 | responseType: 'json' | 调用.json()/.text()等方法 |
2.3 高级功能实现方案
2.3.1 请求取消
const controller = new AbortController(); // 设置超时自动取消 const timeoutId = setTimeout(() => controller.abort(), 5000); fetch('/api/slow', { signal: controller.signal }) .then(response => { clearTimeout(timeoutId); // 处理响应 }) .catch(err => { if (err.name === 'AbortError') { console.log('请求被取消'); } });2.3.2 拦截器模拟
虽然Fetch没有内置拦截器,但可以通过高阶函数实现类似功能:
const createFetchWithInterceptors = (baseFetch) => { return async (input, init) => { // 请求拦截 const modifiedInit = { ...init, headers: { ...init?.headers, 'X-Request-ID': generateUUID() } }; try { const response = await baseFetch(input, modifiedInit); // 响应拦截 if (response.status === 401) { redirectToLogin(); throw new Error('未授权'); } return response; } catch (error) { // 错误处理 logError(error); throw error; } }; }; const enhancedFetch = createFetchWithInterceptors(fetch);3. 性能优化与实战技巧
3.1 减少包体积的实际收益
通过webpack-bundle-analyzer分析一个典型项目:
- 使用axios时:主包体积增加约12KB(gzipped)
- 改用Fetch后:仅需添加3KB的polyfill(针对IE11等旧浏览器)
对于移动端优先的项目,这9KB的差异可能意味着:
- 3G网络下加载时间减少约300ms
- 低端设备解析时间减少约50ms
- 整体页面评分提升5-10分(Lighthouse)
3.2 常见问题解决方案
3.2.1 默认不带cookie
需要显式设置credentials:
fetch('/api/auth', { credentials: 'include' // 相当于axios的withCredentials: true });3.2.2 响应超时处理
结合AbortController的完整实现:
async function fetchWithTimeout(url, options = {}, timeout = 8000) { const controller = new AbortController(); const timer = setTimeout(() => controller.abort(), timeout); try { const response = await fetch(url, { ...options, signal: controller.signal }); clearTimeout(timer); if (!response.ok) { throw new Error(`${response.status} ${response.statusText}`); } return response; } catch (err) { clearTimeout(timer); if (err.name === 'AbortError') { throw new Error(`请求超时(${timeout}ms)`); } throw err; } }3.2.3 上传进度监控
通过读取ReadableStream实现:
async function uploadWithProgress(url, file) { const response = await fetch(url, { method: 'POST', body: file, headers: { 'Content-Type': file.type } }); const reader = response.body.getReader(); const contentLength = +response.headers.get('Content-Length'); let receivedLength = 0; while(true) { const {done, value} = await reader.read(); if (done) break; receivedLength += value.length; const percent = Math.round((receivedLength / contentLength) * 100); updateProgress(percent); // 更新UI进度 } }4. 迁移策略与决策建议
4.1 适合迁移的场景
- 新项目启动:没有历史包袱,可以直接采用现代方案
- 简单请求场景:仅需基础GET/POST请求
- 性能敏感型应用:特别是移动端H5页面
- PWA/SSR应用:需要最小化依赖项
4.2 建议保留axios的情况
- 复杂拦截器链:如统一认证、错误上报等深度定制
- 请求/响应转换:需要自动处理特殊数据格式
- 同时兼容Node和浏览器:axios天然支持同构应用
- 已有大型代码库:迁移成本可能超过收益
4.3 渐进式迁移方案
对于大型现存项目,可以采用混合模式逐步迁移:
// http.js - 统一出口 export const request = (window.fetch ? fetchWrapper : axios); async function fetchWrapper(input, init) { // 在这里实现与axios兼容的API try { const response = await fetch(input, { ...init, headers: { 'Content-Type': 'application/json', ...init?.headers } }); if (!response.ok) throw new Error(response.statusText); return response.json(); } catch (error) { // 统一错误格式 return { status: error.response?.status || 0, message: error.message }; } }5. 生态工具推荐
虽然使用原生API,但这些工具可以提升开发体验:
wretch:轻量级Fetch封装(<2KB)
wretch('/api') .json({data: 'value'}) .post() .json(console.log)redaxios:兼容axios API的微型替代品(约800字节)
const axios = redaxios.create({baseURL: '/api'});ofetch:Nuxt团队开发的智能Fetch工具
$fetch('/api', { retry: 3, timeout: 5000 });
在实际项目中,我通常会先尝试纯Fetch实现,只有当遇到复杂场景时才考虑引入这些轻量工具。这种渐进式的方案既能保持代码简洁,又能在必要时获得额外功能支持。