一篇 JS 的 Fetch API 与 axios 库的详细对比

三葉Leaves Author

本文是我个人在学习 AJAX (也就是 JS 异步编程)时候的学习笔记,假设你已掌握 axios 和 XMLHttpRequest 的情况下撰写。

关于三种请求方式

IE 时代 JS 里发起请求的方式是使用 XMLHttpRequest 对象,可见 使用 XMLHttpRequest 对象。这个方式没有实现异步处理,代码阅读感受比较反直觉。

Axios 是一个成熟的库,封装了 XMLHttpRequestPromise ,并且提供了更多好用的特性和工具,设计哲学是“为开发者多做一点”。

Fetch API 是现代浏览器原生支持的用于替代 XMLHttpRequest 的东西,也是基于 promise 实现的。

我该用哪种

首先,XMLHttpRequest 在现代前端开发中几乎不用了。至于后面的两者,简单总结:

  • 追求专业、原生、细粒度的控制:使用 Fetch api
  • 使用更多现成的特性,大型、工程化项目,或者要兼容 IE 的项目:使用 axios

个人总结的一些区别

设计哲学

axios 设计哲学是为开发者多做点,其提供了很多方便的特性和工具,fetch 则相对低阶,没有这些特性。

请求参数

比如当你想实现这样的请求 URL:
https://example.com/api?username=leaves&password=123

你完全可以设置 axios 的 params 参数实现这一点,但是在 fetch 里你可能就只能自己构建 URLSearchParams 对象来实现:

1
2
3
4
const pname = '浙江省'  
const cname = '温州市'
const queryObj = new URLSearchParams({pname, cname})
const queryStr = queryObj.toString()

之后再使用模板字符串拼接。不过如果用的多的话,你当然也可以自己封装函数模拟 axios 的这些工具。

请求拦截器

axios 自带的一个很好用的特性,可以用来给每一个请求添加 token。
类似的,axios 还可以设置 baseURL。

请求类型

无论是用 axios 还是 XMLHttpRequest,devtools 里看请求类型都是 XHR ,而 fetch api 则会显示 fetch :

这就说明了 fetch api 是一个完全的新东西。

请求头

axios 会自动设置请求头,而 fetch 需要显示强制设置请求头,尤其是 Content-Type

对比下面这两段代码,他们是等价的:

1
2
3
4
5
6
7
8
9
fetch('http://geek.itheima.net/v1_0/authorizations',{  
method: 'POST',
body: JSON.stringify(data),
headers: {
'Content-Type': 'application/json',
'Accept': 'application/json, text/plain, */*',
}
}).then((response) => {
console.log(response)})
1
2
3
4
5
6
axios({  
url: 'http://geek.itheima.net/v1_0/authorizations',
method: 'post',
data: data
}).then((response) => {
console.log(response)})

我们可以看到 fetch 部分的代码里请求头有设置

1
2
3
4
headers: {  
'Content-Type': 'application/json',
'Accept': 'application/json, text/plain, */*',
}

其中:

  • Content-Type 是必须的,否则某些后端会拒绝接受请求。
  • Accept 不设置,fetch 会默认设置成 Accept: */*,而 axios 会默认设置成 Accept: application/json, text/plain, */* ,表示“更期望对方返回 application/json 类型”。

请求体

  • axios 请求体用 data 字段设置,可以直接传入JS 对象
  • fetch 请求体用 body 字段设置,必须是字符串、Blob等,不能是对象。

刚刚请求头那边的示例代码中,我们就将一个对象转换为 JSON 字符串给 body 使用:

1
body: JSON.stringify(data)

响应处理

  • axios 一步到位,直接使用 response.data 即可。response.then() 方法里的形参。
  • fetch 需要两步:
    1. 检查 response.ok(这点下文[[#HTTP 状态错误]]会详细说明)
    2. 调用 response.json()

使用 fetch 和 axios 都会返回一个 promise ,都可以用 .then() 方法接受,但是其中接受到的形参 response 并不是一个东西。

还是拿刚才的代码举例:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
// data 是一个对象,里面存着表单数据
const data = serialize(form,{hash: true, empty: true})

fetch('http://geek.itheima.net/v1_0/authorizations',{
method: 'POST',
body: JSON.stringify(data),
headers: {
'Content-Type': 'application/json',
'Accept': 'application/json, text/plain, */*',
}
}).then((response) => {
console.log(response)})

axios({
url: 'http://geek.itheima.net/v1_0/authorizations',
method: 'post',
data: data
}).then((response) => {
console.log(response)})

两者都打印了 response ,我们来看看里面到底是啥:

图中我们可以看到,fetch api 的 body 里的内容是 ReadableStream 流数据,我们看不到里面具体的内容的(实在想看可以去在 Devtools 的 Network 面板)。而相比之下,axios 的 data 部分已经把数据整理好,能直接使用。

一个巧妙的比喻

  • 想象使用 fetch 是网购了东西收快递,response 就是收到的快递盒,上面可以看到发件人、发件状态等诸多描述,但是就是不能直接用里面的东西;
  • axios 相比之下就是你的私人管家,他帮你代收了件并且拆开,整理好,端盘到你面前给你用。

那到底怎么用 fetch 的 response 里的数据呢?

要看到 response 里面是啥,我们需要再写一个异步代码来解析流数据,而怎么解析可以由我们自己决定,这点充分体现了 fetch 方式的细粒度控制。

为什么设计成非要用异步代码来解析?

这是因为解析数据的过程很耗时,尤其是数据量大的时候。我们总不能卡在主线程等它慢慢解析吧。

通常情况下,我们会解析成 json :

1
const data = await response.json()

不过尽管如此,还是有 Response.blob() 方法,用于类文件数据 以及 Response:formData() 方法 等。

错误处理

axios 的错误处理做的很贴心(下文会提及),但如果对 fetch api 的理解够深刻,我们同样能完成优雅、细致的错误处理。

我们知道 fetch 和 axios 都会返回一个 promise 对象,既然如此那就都有 rejectresolve 。最关键的一个区别:

  • 不管是什么类型的错误,axios 都会返回 reject,被其后的 catch 语句捕获。
  • 仅仅当请求无法发出时(比如断网、DNS 解析失败、CORS 策略阻拦),fetch 的 Promise 才会 reject。而其他HTTP 状态错误或者数据解析错误等,fetch 依然会返回 resolve ,我们需要额外的处理。

Fetch API 中

书接上文,fetch 的 reject 对应着网络层的错误,那么对于其他的错误,我们可以这样处理:

HTTP 状态错误

fetch 的 response 对象有一个布尔值类型的 ok 属性很实用。
response.ok 等价于 response.status >= 200 && response.status < 300,来判断服务器是否成功处理了请求。

通常的实践是,先检查 response.ok 再写一个异步函数处理 response 里的 body 数据流,因为如果不 ok,那你解析了也没集贸用。

数据解析错误

既然需要写一个异步函数处理 response 里的 body 数据流,那这个过程中就有出错的可能(比如服务器返回的响应数据格式有问题,导致JSON 解析错误,response.json() 失败)。

对于这种错误,如果是在 promise 链式调用里处理,那会被最后一个 catch 捕获。如果使用了 async/await 语法糖,那就用 try...catch.. 处理即可。

一些示例代码

我这里写了一个混合了 promise 和 await 的示例,清晰展示了处理三种错误的逻辑:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
fetch('http://geek.itheima.net/v1_0/authorizations', {  
method: 'POST',
body: JSON.stringify(data),
headers: {
'Content-Type': 'application/json',
'Accept': 'application/json, text/plain, */*',
}
}).then(async (response) => {
if (!response.ok) {
// HTTP 错误
throw new Error('服务端返回错误:' + response.status)
}
try {
const data = await response.json()
console.log(data)
} catch (e) {
// 数据解析错误
console.dir('解析数据出错,服务端可能没传正确的 JSON:' + e)
}
}).catch((error) => {
// 网络层错误
console.dir('网络层出错了:' + error.message)
})

下面是一个更详细的仅使用 async/await 的错误处理逻辑。在该逻辑下,fetch 实现了几乎等同于 axios 那么详细的错误处理分层,缺点在于要写一大堆 if 判断语句,这是因为 .then 链式调用过程中的任何错误都会并入异常捕获网结尾的那个 catch。不过在开发实践中,确实更倾向于在一处统一处理所有错误:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
form.addEventListener('submit', async function (e) {
// ...
try {
const response = await fetch(...);

if (!response.ok) {
// 我们手动抛出一个特定格式的错误
throw new Error(`HTTP_ERROR:${response.status}`);
}

const responseData = await response.json();

// ... 成功逻辑 ...

} catch (error) {
// --- 这里是关键的错误分类逻辑 ---

console.log('捕获到的原始 error 对象:', error);

// 1. 判断是否是 HTTP 状态错误
if (error.message.startsWith('HTTP_ERROR:')) {
const status = error.message.split(':')[1];
console.error(`请求失败,HTTP 状态码: ${status}`);
// 在这里可以根据不同的 status code 给出不同的用户提示
if (status === '401') {
alert('登录失败,请检查用户名或密码!');
} else if (status === '404') {
alert('请求的资源不存在。');
} else {
alert('服务器发生未知错误。');
}

// 2. 判断是否是网络错误
// 网络错误通常是 TypeError,且 message 很固定
} else if (error instanceof TypeError && error.message === 'Failed to fetch') {
console.error('网络连接出现问题,无法发送请求。');
alert('网络错误,请检查您的网络连接!');

// 3. 判断是否是 JSON 解析错误
// JSON 解析错误通常是 SyntaxError
} else if (error instanceof SyntaxError) {
console.error('无法解析服务器返回的数据,可能不是有效的 JSON。');
alert('服务器响应格式错误。');

// 4. 其他未知错误
} else {
console.error('发生未知错误:', error);
alert('操作失败,请稍后重试。');
}
}
});

如果不使用 async/await ,标准链式调用的 promise 的写法可能如下:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
fetch('https://api.example.com/data')  
.then(response => {
if (!response.ok) {
// 如果响应状态不是成功,我们手动抛出一个错误,这个错误会被下面的 .catch() 捕获。
throw new Error('网络响应错误,状态码: ' + response.status);
}
// response.json() 方法会读取响应体并将其解析为 JSON。
// 这个方法本身也是异步的,它同样返回一个 Promise!
return response.json();
})
// 处理 response.json() 返回的包含实际数据的 Promise
.then(data => {
// 在这个 .then() 中,我们才能真正拿到解析后的 JSON 数据。
console.log('成功获取数据:', data);
// 在这里执行你对数据的操作
})
// 捕获整个链条中可能发生的任何错误
.catch(error => {
// 这个 catch 块是全能的,它会捕获:
// 1. 网络层错误 (fetch 本身就失败了)
// 2. 我们手动抛出的 HTTP 状态码错误
// 3. response.json() 解析失败的错误
console.error('Fetch 操作中出现问题:', error);

// 如果你想知道具体是哪种错误,可以检查 error 的类型
if (error instanceof SyntaxError) {
console.error("解析 JSON 失败!响应体可能不是有效的 JSON。");
} else if (error.message.includes('HTTP')) {
console.error("服务器返回了错误的状态码。");
} else {
console.error("可能是网络连接问题。");
}
})
// 第四步(可选):无论成功还是失败,最后都会执行
.finally(() => {
console.log('Fetch 请求已完成。');
// 可以在这里放置一些清理代码,比如隐藏加载动画。
});

axios 中

axios 在错误处理上做了一层非常贴心的标准化封装。无论底层的错误是来自 XMLHttpRequest (浏览器)、http 模块 (Node.js),还是 axios 自己的逻辑(比如超时、取消请求),它都会将错误统一包装成一个特定的 Error 对象,然后 reject 出来(之后可以被 catch 语句接受)。

当 axios 请求失败时,.catch(error) 捕获到的 error 对象通常包含以下关键信息:

  • error.isAxiosError: 一个布尔值,true,可以用来方便地判断这是不是一个 axios 抛出的标准错误。

  • error.message: 错误的描述信息,比如 Network Error,或者 Request failed with status code 404。

  • error.config: 发起请求时的配置对象,包含了 url, method, headers, data 等所有信息,非常便于调试。

  • error.code: 错误码,比如 ECONNABORTED (超时)。

  • error.response这是最重要的部分! 如果错误是服务器返回的(即 HTTP 状态码不是 2xx),error.response 就会存在。它包含:

    • error.response.data: 服务器返回的响应体(比如 { “message”: “用户名或密码错误” })。这是我们最常用来给用户展示错误信息的地方。

    • error.response.status: HTTP 状态码,如 401, 404, 500。

    • error.response.headers: 服务器返回的响应头。

  • error.request: 如果请求已发出但没有收到响应(比如网络错误),这个属性会存在。它通常是底层的 XMLHttpRequest 实例或 Node.js 的 ClientRequest 实例。

axios 错误处理示例
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
axios.get('https://api.example.com/non-existent-page')
.then(response => {
// ...
})
.catch(error => {
console.dir(error); // 打印整个 axios error 对象,信息非常丰富

if (error.response) {
// 请求成功发出,并且服务器也响应了状态码,但状态码超出了 2xx 的范围
console.log('服务器返回了错误:');
console.log('状态码:', error.response.status); // e.g., 404
console.log('响应数据:', error.response.data); // e.g., { message: "Not Found" }
console.log('响应头:', error.response.headers);
} else if (error.request) {
// 请求已经成功发起,但没有收到响应
// `error.request` 在浏览器中是 XMLHttpRequest 的实例
console.log('网络错误,未收到服务器响应:', error.request);
} else {
// 在设置请求时发生了一些事情,触发了一个错误
console.log('请求配置错误:', error.message);
}
console.log('原始请求配置:', error.config);
});
  • 标题: 一篇 JS 的 Fetch API 与 axios 库的详细对比
  • 作者: 三葉Leaves
  • 创建于 : 2025-06-18 00:00:00
  • 更新于 : 2025-07-10 13:40:50
  • 链接: https://blog.oksanye.com/6a6fd611eb6b/
  • 版权声明: 本文章采用 CC BY-NC-SA 4.0 进行许可。
评论