Axios 是目前前端领域最流行的 HTTP 客户端之一。它基于 Promise,同时提供了一系列 Fetch API 不具备或实现起来较为繁琐的能力,真正让网络请求的工程化管理变得简单。在实际项目中,我们几乎不会裸用 Axios,而是将其封装成符合业务需求的请求模块。本节就围绕请求模块的核心能力展开:拦截器、取消请求、错误处理、并发请求。
16.3.1 拦截器:请求与响应的统一处理
拦截器是 Axios 最具工程价值的特性之一。它允许你在一批请求被发送前或响应返回后,统一执行某些逻辑,例如添加 token、日志记录、全局 loading 控制、响应数据脱壳等。
import axios from 'axios';
const request = axios.create({
baseURL: 'https://api.example.com',
timeout: 10000,
});
// 请求拦截器:常用于注入 token
request.interceptors.request.use(
(config) => {
const token = localStorage.getItem('token');
if (token) {
config.headers.Authorization = `Bearer ${token}`;
}
return config;
},
(error) => Promise.reject(error)
);
// 响应拦截器:统一处理错误码、提取 data
request.interceptors.response.use(
(response) => {
// 这里可根据后端约定的结构做数据解包
const res = response.data;
if (res.code !== 200) {
// 业务错误,可统一提示
console.error('接口返回错误:', res.message);
return Promise.reject(new Error(res.message || 'Error'));
}
return res.data;
},
(error) => {
// HTTP 错误(如 401、500)
if (error.response) {
const { status } = error.response;
if (status === 401) {
// 跳转登录
}
}
return Promise.reject(error);
}
);
export default request;
通过封装 request 实例,后续所有接口调用都自动携带 token、统一拦截错误,业务代码中只需关心成功结果。注意拦截器可以添加多个,执行顺序为:请求拦截器逆序执行,响应拦截器顺序执行。
16.3.2 取消请求:避免资源浪费与竞态问题
在单页应用中,用户快速切换页面或重复点击按钮可能导致多余的请求堆积,既浪费带宽,又可能引发状态错乱。Axios 提供了基于 CancelToken(v0.22.0 起也支持 AbortController)的请求取消能力。
import { ref, onUnmounted } from 'vue'; // 以 Vue 为例
function useCancelableRequest() {
const controller = ref(null);
const fetchData = async () => {
// 取消上一次未完成的请求
if (controller.value) {
controller.value.abort();
}
// 创建新的 AbortController
controller.value = new AbortController();
try {
const response = await axios.get('/api/data', {
signal: controller.value.signal,
});
return response.data;
} catch (error) {
if (axios.isCancel(error)) {
console.log('请求被取消:', error.message);
} else {
throw error;
}
}
};
// 组件卸载时清理
onUnmounted(() => {
if (controller.value) {
controller.value.abort();
}
});
return { fetchData };
}
现代 Axios 推荐使用 AbortController / AbortSignal,它也是 Web 标准的一部分。封装时只需将 signal 通过配置传入,Axios 会自动在 abort() 时终止请求并抛出 Cancel 类型的错误。竞态处理(例如搜索框联想)也可用同一思路:每次新请求前取消前一次即可。
16.3.3 错误处理:分层与降级
网络请求的错误需要分层次处理,避免所有错误都在业务代码中散落。通常封装层会划分三个级别:
- 网络级错误:如超时、断网、DNS 解析失败。Axios 会抛出一个没有
response对象的错误,可以通过error.code === 'ECONNABORTED'检测超时。 - HTTP 状态码错误:如 400、404、500 等。这些错误会有
error.response,拦截器中可根据状态码做全局处理(如 401 自动重定向登录)。 - 业务错误:HTTP 200 但返回的
code非 0 或非 200。可在响应拦截器中统一判断并 Toast 提示,但需保留一些错误让调用方自行处理(例如表单校验错误需在表单内展示)。
封装后的用法示例:
// 业务调用
import request from './request';
async function getUserInfo() {
try {
const data = await request.get('/user/info');
return data;
} catch (error) {
// 已由拦截器提示过的全局错误(如 401),这里可选择忽略或补充处理
// 如果是需要特殊处理的业务错误,可在此处消费
console.log('请求失败,执行降级逻辑');
return null; // 降级返回默认值
}
}
这种分层让通用错误“一劳永逸”,业务异常“精准控制”。
16.3.4 并发请求:管理多个异步操作
Axios 提供了静态方法 all 和 spread(基于 Promise.all),可以并发发送多个请求,并在全部成功后一次性获得结果:
function fetchDashboardData() {
return axios.all([
axios.get('/api/user'),
axios.get('/api/orders'),
axios.get('/api/notifications'),
]).then(
axios.spread((userRes, ordersRes, notificationsRes) => {
return {
user: userRes.data,
orders: ordersRes.data,
notifications: notificationsRes.data,
};
})
);
}
如果其中一个请求失败,整体将以失败告终。在实际封装中,我们可能希望部分请求失败不影响其他,可以使用 Promise.allSettled 或为每个请求单独添加 catch:
async function fetchDataSafely() {
const [user, orders] = await Promise.allSettled([
axios.get('/api/user').then(r => r.data),
axios.get('/api/orders').then(r => r.data),
]);
return {
user: user.status === 'fulfilled' ? user.value : null,
orders: orders.status === 'fulfilled' ? orders.value : null,
};
}
此外,Axios 的 request、get 等方法都返回 Promise,所以可以直接与现代异步流(Promise.all、async/await)无缝结合。
16.3.5 小结与封装建议
封装 Axios 的本质是将重复的、通用的逻辑抽离,让业务开发聚焦于数据消费。一个成熟的项目通常会暴露一个 request 实例,包含以下能力:
- 可以配置多个
baseURL实例以区分不同服务。 - 请求拦截器自动注入 token、添加公共参数、处理重复请求。
- 响应拦截器统一解包、处理错误码、支持数据转换。
- 内置取消请求机制,供页面切换或竞态场景使用。
- 错误处理分层清晰,支持全局提示和局部自定义处理。
- 提供
get、post、upload等语义化方法,并可扩展重试、节流等功能。
如果你正在使用 React 或 Vue,还可以将请求封装为 Hook / Composable,进一步简化组件内的使用。无论形式如何,核心目标始终如一:让网络请求更可控、更一致、更少重复代码。