DrillLab
第 51 / 105 道51 / 105 · #387

fetch 和 axios 的区别

What is the difference between making server requests via fetch and axios?

先自己答,再往下看Answer it yourself first

一句话:fetch浏览器内置的, 很基础;axios 是第三方库, 把常用的事都替你做了。

fetchaxios
依赖,浏览器/Node 18+ 内置要装(约 13 KB)
4xx / 5xx不 reject, 要自己查 res.ok自动抛错
JSON要手动 await res.json()自动解析到 data
超时要自己配 AbortControllertimeout 一个选项
拦截器没有,要自己包一层内置(统一加 token、统一处理 401)
上传进度很麻烦支持
Node 里可用18+ 才有一直可以

「404 不 reject」是这题的核心考点, 也是真实 bug 的来源:不查res.ok 就会把错误页当数据渲染。从 axios 转过来的人最容易漏这一条, 因为 axios 会自己抛。

怎么选:简单项目、在意包体积、 或者只发几个请求 → fetch包一个自己的小 wrapper; 需要拦截器 / 统一错误处理 / 上传进度,或者要兼容老 Node → axios
更常见的现实答案:用TanStack Query / SWR管缓存和请求状态, 底下用哪个都行 —— 因为fetchaxios都不管缓存、去重、重试。 能这么答说明你想过分层。

In one line: fetch is built into the browser and very bare-bones; axios is a third-party library that does the routine work for you.

fetchaxios
DependencyNone, built into browsers and Node 18+Must be installed (about 13 KB)
4xx / 5xxDoes not reject; you check res.ok yourselfThrows for you
JSONYou call await res.json() by handParsed into data already
TimeoutWire up an AbortController yourselfOne timeout option
InterceptorsNone; wrap it yourselfBuilt in (attach a token everywhere, handle 401 in one place)
Upload progressPainfulSupported
Available in NodeOnly from 18Always has been

“404 does not reject” is the heart of this question, and a real source of bugs: skip the res.ok check and you render an error page as if it were data. People coming over from axios miss this one most often, because axios throws on their behalf.

How to choose: a small project, a tight bundle budget, or only a handful of requests → fetch with your own small wrapper. Interceptors, one place for error handling, upload progress, or an old Node to support → axios.
The more realistic answer: reach for TanStack Query or SWR to manage caching and request state, and whichever one sits underneath hardly matters — because neither fetch nor axios handles caching, deduplication or retries. Answering that way shows you have thought about the layers.

JavaScript两者的核心差别与自制 wrapperThe main difference, and a wrapper of your own示意Illustrative
1// fetch:404 也是「成功」,必须自己判
2const res = await fetch(url);
3if (!res.ok) throw new Error(`HTTP ${res.status}`); // ← 漏了这行就会出 bug
4const data = await res.json();
5
6// axios:非 2xx 自己抛,data 已经解析好
7const { data } = await axios.get(url);
8
9// 自己给 fetch 包一层,就能补上大部分差距
10async function request(url, opts = {}) {
11 const c = new AbortController();
12 const t = setTimeout(() => c.abort(), opts.timeout ?? 10000);
13 try {
14 const res = await fetch(url, { ...opts, signal: c.signal });
15 if (!res.ok) throw new Error(`HTTP ${res.status}`);
16 return await res.json();
17 } finally {
18 clearTimeout(t);
19 }
20}
1// fetch: a 404 also counts as "success", so you have to check it yourself
2const res = await fetch(url);
3if (!res.ok) throw new Error(`HTTP ${res.status}`); // ← leave this line out and you get a bug
4const data = await res.json();
5
6// axios: it throws on anything that is not 2xx, and data is already parsed
7const { data } = await axios.get(url);
8
9// Wrap fetch yourself and you close most of the gap
10async function request(url, opts = {}) {
11 const c = new AbortController();
12 const t = setTimeout(() => c.abort(), opts.timeout ?? 10000);
13 try {
14 const res = await fetch(url, { ...opts, signal: c.signal });
15 if (!res.ok) throw new Error(`HTTP ${res.status}`);
16 return await res.json();
17 } finally {
18 clearTimeout(t);
19 }
20}