0%

Fetch 与 Axios 对比——现代 HTTP 请求库选型指南

今天突然停电…

介绍

  在网络编程中,HTTP 请求是前端与后端交互的基础。在现代 Javascript 中,我们有两种主要的方式来进行网络请求:原生的 Fetch API 和流行的 Axios 库。今天我们就来深入探讨这两种方式的特点、用法以及在实际项目中的选择策略。

Fetch API 详解

Fetch 基本概念

  Fetch API 是现代浏览器原生提供的用于获取资源的接口,它提供了一个全局的fetch()方法,返回一个 Promise,这个 Promise 会 resolve 为 Response 对象。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
// 最简单的 fetch 请求 fetch('/API/users')
.then(response => response.Json())
.then(data => console.log(data))
.catch(error => console.error('Error:', error));

// 使用 async/await 语法 async function getUsers() {
try {
const response = await fetch('/API/users');
const data = await response.Json();
console.log(data);
} catch (error) {
console.error('Error:', error);
}
}

Fetch 请求配置

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
// POST 请求示例 async function createUser(userData) {
const response = await fetch('/API/users', {
method: 'POST', // 请求方法 headers: {
'Content-Type': 'application/Json',
'Authorization': 'Bearer token'
},
body: Json.stringify(userData) // 请求体
});

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

const result = await response.Json();
return result;
}

// GET 请求示例 async function getUser(id) {
const response = await fetch(`/API/users/${id}`, {
method: 'GET',
headers: {
'Accept': 'application/Json',
'Authorization': 'Bearer token'
}
});

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

return await response.Json();
}

Fetch 响应处理

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
// 响应处理的不同方式 async function handleResponse() {
const response = await fetch('/API/data');

// 1. Json 响应 if (response.headers.get('content-type').includes('application/Json')) {
const jsonData = await response.Json();
console.log(jsonData);
}

// 2. 文本响应 const textData = await response.text();
console.log(textData);

// 3. Blob 响应(如图片、文件等)
const blobData = await response.blob();
const imageUrl = URL.createObjectURL(blobData);
console.log(imageUrl);

// 4. ArrayBuffer 响应(如二进制数据)
const arrayBuffer = await response.arrayBuffer();
console.log(arrayBuffer);

// 5. FormData 响应 const formData = await response.formData();
console.log(formData);
}

// 响应状态检查 async function checkStatus() {
const response = await fetch('/API/check-status');

// 检查响应状态 switch (response.status) {
case 200:
console.log('Success');
break;
case 401:
console.log('Unauthorized');
break;
case 404:
console.log('Not Found');
break;
case 500:
console.log('Server Error');
break;
default:
console.log(`Status: ${response.status}`);
}

return response;
}

Fetch 拦截和中间件模式

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
52
53
54
55
56
57
58
59
60
61
62
63
// 封装 Fetch 请求,添加统一的请求/响应处理 class FetchClient {
constructor(baseURL = '') {
this.baseURL = baseURL;
this.interceptors = {
request: [],
response: []
};
}

// 添加请求拦截器 addRequestInterceptor(interceptor) {
this.interceptors.request.push(interceptor);
}

// 添加响应拦截器 addResponseInterceptor(interceptor) {
this.interceptors.response.push(interceptor);
}

// 执行请求 async request(config) {
// 处理请求拦截器 let processedConfig = { ...config };
for (const interceptor of this.interceptors.request) {
processedConfig = await interceptor(processedConfig);
}

try {
// 发起请求 const url = this.baseURL + processedConfig.url;
const response = await fetch(url, processedConfig);

// 处理响应拦截器 let processedResponse = response;
for (const interceptor of this.interceptors.response) {
processedResponse = await interceptor(processedResponse);
}

return processedResponse;
} catch (error) {
throw error;
}
}
}

// 使用示例 const client = new FetchClient('https://API.example.com');

// 请求拦截器:添加认证 token
client.addRequestInterceptor(async (config) => {
const token = localStorage.getItem('token');
if (token) {
config.headers = {
...config.headers,
'Authorization': `Bearer ${token}`
};
}
return config;
});

// 响应拦截器:统一错误处理 client.addResponseInterceptor(async (response) => {
if (!response.ok) {
if (response.status === 401) {
// 处理未授权 localStorage.removeItem('token');
window.location.href = '/login';
}
throw new Error(`Request failed: ${response.status}`);
}
return response;
});

Fetch 的局限性

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
// Fetch 的一些局限性示例

// 1. 错误处理 async function handleError() {
const response = await fetch('/API/nonexistent');
// 注意:即使请求失败(如404),fetch 也不会 reject
// 只有网络错误才会触发 catch
console.log(response.ok); // false,但不会进入 catch
console.log(response.status); // 404
}

// 2. 超时处理 function fetchWithTimeout(url, options = {}, timeout = 8000) {
return Promise.race([
fetch(url, options),
new Promise((_, reject) =>
setTimeout(() => reject(new Error('Request timeout')), timeout)
)
]);
}

// 3. 请求取消
// Fetch 本身不支持取消,需要 AbortController
function cancellableFetch(url, signal) {
return fetch(url, { signal });
}

// 使用 AbortController
const controller = new AbortController();
const { signal } = controller;

cancellableFetch('/API/data', signal)
.then(response => response.Json())
.then(data => console.log(data))
.catch(error => {
if (error.name === 'AbortError') {
console.log('Request was cancelled');
} else {
console.error('Error:', error);
}
});

// 取消请求
// controller.abort();

Axios 详解

Axios 基本使用

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
// Axios 安装和基本配置
// npm install axios

import axios from 'axios';

// 基本 GET 请求 async function getPosts() {
try {
const response = await axios.get('/API/posts');
console.log(response.data);
console.log(response.status);
console.log(response.headers);
} catch (error) {
if (error.response) {
// 服务器返回错误状态码 console.error('Response error:', error.response.data);
console.error('Status:', error.response.status);
} else if (error.request) {
// 请求已发出但没有收到响应 console.error('Request error:', error.request);
} else {
// 其他错误 console.error('Error:', error.message);
}
}
}

// 不同请求方法 const API = {
// GET
getUsers: () => axios.get('/users'),

// POST
createUser: (userData) => axios.post('/users', userData),

// PUT
updateUser: (id, userData) => axios.put(`/users/${id}`, userData),

// DELETE
deleteUser: (id) => axios.delete(`/users/${id}`)
};

Axios 高级特性

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
52
// Axios 实例配置 const apiClient = axios.create({
baseURL: 'https://API.example.com',
timeout: 10000,
headers: {
'Content-Type': 'application/Json',
'X-Requested-With': 'XmlHttpRequest'
}
});

// 请求拦截器 apiClient.interceptors.request.use(
config => {
// 在发送请求之前做些什么 console.log('Request sent:', config);

// 添加 token
const token = localStorage.getItem('token');
if (token) {
config.headers.Authorization = `Bearer ${token}`;
}

return config;
},
error => {
// 对请求错误做些什么 console.error('Request error:', error);
return Promise.reject(error);
}
);

// 响应拦截器 apiClient.interceptors.response.use(
response => {
// 对响应数据做点什么 console.log('Response received:', response);
return response;
},
error => {
// 对响应错误做点什么 if (error.response?.status === 401) {
// 处理未授权 localStorage.removeItem('token');
window.location.href = '/login';
}
return Promise.reject(error);
}
);

// 取消请求 const CancelToken = axios.CancelToken;
let cancel;

const response = await axios.get('/API/data', {
cancelToken: new CancelToken(c => {
cancel = c;
})
});

// 取消请求
// cancel('Operation canceled by the user.');

Axios 实用功能

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
52
53
54
55
56
57
58
// 进度监控 function uploadFile(file) {
return axios.post('/upload', file, {
headers: {
'Content-Type': 'multipart/form-data'
},
onUploadProgress: (progressEvent) => {
const percentCompleted = Math.round(
(progressEvent.loaded * 100) / progressEvent.total
);
console.log(`Upload progress: ${percentCompleted}%`);
}
});
}

// 下载进度 function downloadFile(url) {
return axios.get(url, {
responseType: 'blob',
onDownloadProgress: (progressEvent) => {
const percentCompleted = Math.round(
(progressEvent.loaded * 100) / progressEvent.total
);
console.log(`Download progress: ${percentCompleted}%`);
}
});
}

// 多个请求并发 async function concurrentRequests() {
try {
const [usersRes, postsRes, commentsRes] = await Promise.all([
axios.get('/API/users'),
axios.get('/API/posts'),
axios.get('/API/comments')
]);

return {
users: usersRes.data,
posts: postsRes.data,
comments: commentsRes.data
};
} catch (error) {
console.error('One or more requests failed:', error);
}
}

// 使用 axios.all(已被废弃,但仍可用)
async function deprecatedConcurrent() {
const responses = await axios.all([
axios.get('/API/users'),
axios.get('/API/posts')
]);

return axios.spread((usersRes, postsRes) => {
return {
users: usersRes.data,
posts: postsRes.data
};
})(...responses);
}

Fetch vs Axios 对比

功能对比表

特性FetchAxios
浏览器支持现代浏览器原生支持需要引入库
错误处理需要手动检查 status自动处理 HTTP 错误
请求中断需要 AbortController内置 CancelToken
请求/响应拦截需要手动封装内置拦截器
进度监控不支持支持上传/下载进度
默认 Json 解析需要手动调用.Json()自动 Json 解析
自动转换需要手动处理请求/响应数据转换
XSRF 保护需要手动实现内置支持
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
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
// 具体对比示例

// 1. 错误处理对比
// Fetch - 需要手动检查 async function fetchErrorHandling() {
const response = await fetch('/API/users');

// 必须手动检查状态 if (!response.ok) {
throw new Error(`HTTP error! status: ${response.status}`);
}

return response.Json();
}

// Axios - 自动处理 async function axiosErrorHandling() {
// 4xx 和5xx 状态码会自动抛出错误 const response = await axios.get('/API/users');
return response.data; // 自动解析 Json
}

// 2. 请求取消对比
// Fetch - 需要 AbortController
function fetchWithCancel() {
const controller = new AbortController();

const promise = fetch('/API/data', {
signal: controller.signal
});

// 取消请求 controller.abort();

return promise;
}

// Axios - 内置取消机制 function axiosWithCancel() {
const source = axios.CancelToken.source();

const promise = axios.get('/API/data', {
cancelToken: source.token
});

// 取消请求 source.cancel('Operation canceled by the user.');

return promise;
}

// 3. 拦截器对比
// Fetch - 需要手动实现 class CustomFetch {
constructor() {
this.interceptors = [];
}

use(interceptor) {
this.interceptors.push(interceptor);
}

async request(config) {
// 执行请求拦截器 for (const interceptor of this.interceptors) {
config = await interceptor.request(config);
}

const response = await fetch(config.url, config);

// 执行响应拦截器 for (const interceptor of this.interceptors) {
response = await interceptor.response(response);
}

return response;
}
}

// Axios - 内置拦截器 axios.interceptors.request.use(request => {
console.log('Request sent:', request);
return request;
});

axios.interceptors.response.use(response => {
console.log('Response received:', response);
return response;
});

性能对比

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
// 包大小对比
// Fetch: 原生 API,无额外体积
// Axios: 约5KB gzip 压缩后

// 基准测试示例 async function performanceTest() {
const iterations = 100;

// Fetch 性能测试 console.time('Fetch requests');
const fetchPromises = Array.from({ length: iterations }, () =>
fetch('/API/test').then(r => r.Json())
);
await Promise.all(fetchPromises);
console.timeEnd('Fetch requests');

// Axios 性能测试 console.time('Axios requests');
const axiosPromises = Array.from({ length: iterations }, () =>
axios.get('/API/test').then(r => r.data)
);
await Promise.all(axiosPromises);
console.timeEnd('Axios requests');
}

实际应用场景选择

何时使用 Fetch

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 的场景

// 1. 轻量级项目,不需要复杂功能 const simpleAPI = {
getProfile: async () => {
const response = await fetch('/API/profile');
if (!response.ok) throw new Error('Failed to fetch profile');
return response.Json();
}
};

// 2. 需要充分利用原生 API 特性 async function streamExample() {
const response = await fetch('/API/large-data');
const reader = response.body.getReader();

while (true) {
const { done, value } = await reader.read();
if (done) break;
// 处理数据块 console.log('Chunk received:', value);
}
}

// 3. 极简主义,减少依赖 const minimalAPI = {
request: async (url, options = {}) => {
const response = await fetch(url, {
headers: {
'Content-Type': 'application/Json',
...options.headers
},
...options
});

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

return response.Json();
}
};

何时使用 Axios

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
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
// 适合使用 Axios 的场景

// 1. 复杂的企业级应用 class APIService {
constructor() {
this.client = axios.create({
baseURL: process.env.API_BASE_URL,
timeout: 10000,
headers: {
'Content-Type': 'application/Json'
}
});

// 添加拦截器 this.setupInterceptors();
}

setupInterceptors() {
// 请求拦截器 this.client.interceptors.request.use(
config => {
const token = localStorage.getItem('authToken');
if (token) {
config.headers.Authorization = `Bearer ${token}`;
}
return config;
},
error => Promise.reject(error)
);

// 响应拦截器 this.client.interceptors.response.use(
response => response,
error => {
if (error.response?.status === 401) {
// 重新登录逻辑 this.handleUnauthorized();
}
return Promise.reject(error);
}
);
}

handleUnauthorized() {
localStorage.removeItem('authToken');
window.location.href = '/login';
}

// API 方法 login(credentials) {
return this.client.post('/auth/login', credentials);
}

getProfile() {
return this.client.get('/user/profile');
}

uploadFile(file, onProgress) {
const formData = new FormData();
formData.append('file', file);

return this.client.post('/upload', formData, {
headers: {
'Content-Type': 'multipart/form-data'
},
onUploadProgress: onProgress
});
}
}

// 2. 需要大量 HTTP 请求处理的应用 const dataService = {
// 并发请求处理 async loadDashboardData() {
try {
const results = await axios.all([
axios.get('/API/stats'),
axios.get('/API/users'),
axios.get('/API/reports')
]);

return axios.spread((stats, users, reports) => ({
stats: stats.data,
users: users.data,
reports: reports.data
}))(results[0], results[1], results[2]);
} catch (error) {
console.error('Failed to load dashboard data:', error);
throw error;
}
},

// 带有重试机制的请求 async requestWithRetry(url, maxRetries = 3, retryDelay = 1000) {
let lastError;

for (let i = 0; i < maxRetries; i++) {
try {
return await axios.get(url);
} catch (error) {
lastError = error;
if (i < maxRetries - 1) {
await new Promise(resolve => setTimeout(resolve, retryDelay));
}
}
}

throw lastError;
}
};

最佳实践

Fetch 最佳实践

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
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
// 封装 Fetch 以提供更好体验 class SafeFetch {
constructor(options = {}) {
this.baseURL = options.baseURL || '';
this.defaultHeaders = options.headers || {};
this.timeout = options.timeout || 10000;
}

async request(url, options = {}) {
const controller = new AbortController();
const timeoutId = setTimeout(() => controller.abort(), this.timeout);

try {
const fullUrl = this.baseURL + url;
const config = {
headers: {
'Content-Type': 'application/Json',
...this.defaultHeaders,
...options.headers
},
...options,
signal: controller.signal
};

const response = await fetch(fullUrl, config);

clearTimeout(timeoutId);

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

// 尝试解析 Json
const contentType = response.headers.get('content-type');
if (contentType && contentType.includes('application/Json')) {
return await response.Json();
}

return await response.text();
} catch (error) {
clearTimeout(timeoutId);

if (error.name === 'AbortError') {
throw new Error('Request timeout');
}

throw error;
}
}

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 SafeFetch({
baseURL: 'https://API.example.com',
timeout: 5000,
headers: {
'X-API-Key': 'your-API-key'
}
});

// 实际使用 try {
const user = await API.get('/users/123');
console.log(user);
} catch (error) {
console.error('API call failed:', error.message);
}

Axios 最佳实践

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
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
// 创建可复用的 Axios 配置 import axios from 'axios';

// 创建 API 实例 const createAPIInstance = (config) => {
const instance = axios.create({
baseURL: config.baseURL,
timeout: config.timeout || 10000,
headers: {
'Content-Type': 'application/Json',
...config.headers
}
});

// 添加请求拦截器 instance.interceptors.request.use(
(request) => {
// 添加请求开始逻辑(如 loading 显示)
console.log(`Making request to ${request.url}`);

// 添加认证 token
const token = localStorage.getItem('access_token');
if (token && request.headers) {
request.headers.Authorization = `Bearer ${token}`;
}

return request;
},
(error) => {
return Promise.reject(error);
}
);

// 添加响应拦截器 instance.interceptors.response.use(
(response) => {
// 响应成功处理 console.log(`Response from ${response.config.url}:`, response.status);
return response;
},
(error) => {
// 统一错误处理 const { response, request, message } = error;

if (response) {
// 服务器响应错误 console.error(`Server Error: ${response.status}`, response.data);

// 特殊状态码处理 switch (response.status) {
case 401:
// Token 过期处理 localStorage.removeItem('access_token');
window.location.href = '/login';
break;
case 403:
// 权限不足 console.error('Access denied');
break;
case 500:
// 服务器内部错误 console.error('Server error occurred');
break;
}
} else if (request) {
// 请求已发出但未收到响应 console.error('Network error:', request);
} else {
// 其他错误 console.error('Error:', message);
}

return Promise.reject(error);
}
);

return instance;
};

// API 服务封装 class ApiService {
constructor() {
this.API = createAPIInstance({
baseURL: process.env.REACT_APP_API_BASE_URL || 'http://localhost:3001/API'
});
}

// 通用请求方法 async request(method, url, data = null, options = {}) {
try {
const response = await this.API({
method,
url,
data,
...options
});

return response.data;
} catch (error) {
throw this.handleError(error);
}
}

// CRUD 方法 get(url, params = {}) {
return this.request('GET', url, null, { params });
}

post(url, data = {}) {
return this.request('POST', url, data);
}

put(url, data = {}) {
return this.request('PUT', url, data);
}

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

// 自定义错误处理 handleError(error) {
if (error.response) {
return {
status: error.response.status,
message: error.response.data?.message || 'Server error occurred',
data: error.response.data
};
} else if (error.request) {
return {
status: 0,
message: 'Network error occurred',
data: null
};
} else {
return {
status: -1,
message: error.message,
data: null
};
}
}
}

// 使用示例 const apiService = new ApiService();

// 使用 API
async function useApi() {
try {
const users = await apiService.get('/users');
console.log('Users:', users);

const newUser = await apiService.post('/users', {
name: 'John Doe',
email: 'john@example.com'
});
console.log('Created user:', newUser);
} catch (error) {
console.error('API error:', error);
}
}

总结

  • Fetch 是现代浏览器原生提供的 API,轻量且功能强大,但需要手动处理一些细节
  • Axios 提供了更友好的 API 和丰富的功能,适合复杂的企业级应用
  • 在选择时应考虑项目规模、团队经验、维护成本等因素
  • Fetch 适合轻量级项目或需要原生 API 特性的场景
  • Axios 适合需要复杂请求处理、拦截器、进度监控等功能的场景
  • 两者都有各自的优缺点,关键是选择最适合项目需求的工具

今天停电的体验让我深刻体会到,就像网络请求是现代 web 应用的”电力供应”一样,没有稳定可靠的 HTTP 请求,很多功能都无法正常运行。选择合适的请求工具就像选择稳定的电力供应商一样重要!

扩展阅读

  1. Fetch API MDN 文档
  2. Axios 官方文档
  3. 现代 Javascript 网络请求指南
  4. HTTP 请求最佳实践

练习建议

  1. 创建一个简单的 API 客户端,比较 Fetch 和 Axios 的实现差异
  2. 实现一个带有请求缓存功能的请求库
  3. 创建一个模拟服务器,测试不同网络状况下的请求处理
  4. 研究不同请求库的源码,理解其实现原理
bulb