人人都会AI编程

16.3 Axios 封装:拦截器、取消请求、错误处理、并发请求

更新时间:2026-07-11

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 错误处理:分层与降级

网络请求的错误需要分层次处理,避免所有错误都在业务代码中散落。通常封装层会划分三个级别:

  1. 网络级错误:如超时、断网、DNS 解析失败。Axios 会抛出一个没有 response 对象的错误,可以通过 error.code === 'ECONNABORTED' 检测超时。
  2. HTTP 状态码错误:如 400、404、500 等。这些错误会有 error.response,拦截器中可根据状态码做全局处理(如 401 自动重定向登录)。
  3. 业务错误: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 提供了静态方法 allspread(基于 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 的 requestget 等方法都返回 Promise,所以可以直接与现代异步流(Promise.allasync/await)无缝结合。

16.3.5 小结与封装建议

封装 Axios 的本质是将重复的、通用的逻辑抽离,让业务开发聚焦于数据消费。一个成熟的项目通常会暴露一个 request 实例,包含以下能力:

  • 可以配置多个 baseURL 实例以区分不同服务。
  • 请求拦截器自动注入 token、添加公共参数、处理重复请求。
  • 响应拦截器统一解包、处理错误码、支持数据转换。
  • 内置取消请求机制,供页面切换或竞态场景使用。
  • 错误处理分层清晰,支持全局提示和局部自定义处理。
  • 提供 getpostupload 等语义化方法,并可扩展重试、节流等功能。

如果你正在使用 React 或 Vue,还可以将请求封装为 Hook / Composable,进一步简化组件内的使用。无论形式如何,核心目标始终如一:让网络请求更可控、更一致、更少重复代码