Axios CancelToken 小解

10 阅读2分钟

一、CancelToken 原理

CancelToken 是 Axios 提供的请求取消机制,其核心原理基于发布订阅模式和 Promise 控制:

  1. 发布订阅模式‌:CancelToken 实例通过 subscribe 方法订阅取消消息,当外部调用 cancel 方法时会触发订阅器取消请求。

  2. Promise 控制‌:

    • source 中的 token 是一个处于 pending 状态的 Promise
    • cancel 方法是 token 的 Promise 对象的 resolve 触发器
    • 当调用 cancel 时,Promise 状态变为 resolved,触发 then 回调执行 xhr.abort()
  3. 内部流程‌:

    • 创建 CancelToken 时会传入一个 executor 函数
    • executor 接收 cancel 函数作为参数
    • 调用 cancel 后,Axios 会检测到并取消对应请求

二、基本用法

1. 取消单个请求

const { CancelToken } = axios;
let cancel;

axios.get('/user/12345', {
  cancelToken: new CancelToken(function executor(c) {
    // executor 函数接收 cancel 函数作为参数
    cancel = c;
  })
});

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

2. 取消多个请求

const source = CancelToken.source();

axios.get('/user/12345', {
  cancelToken: source.token
});

axios.post('/user/12345', {
  name: 'new name'
}, {
  cancelToken: source.token
});

// 取消所有使用该 token 的请求
source.cancel('Operation canceled by user');

三、实际应用示例

1. 防止重复请求

let pendingRequest = null;

function fetchData() {
  // 如果已有请求在进行,先取消
  if (pendingRequest) {
    pendingRequest('Cancel previous request');
  }
  
  const source = CancelToken.source();
  pendingRequest = source.cancel;
  
  return axios.get('/api/data', {
    cancelToken: source.token
  }).finally(() => {
    pendingRequest = null;
  });
}

2. 路由切换时取消请求

// Vue 路由守卫示例
router.beforeEach((to, from, next) => {
  // 取消所有 pending 的请求
  if (window.axiosPendingRequests) {
    window.axiosPendingRequests.forEach(request => {
      request.cancel('Navigation canceled');
    });
    window.axiosPendingRequests = [];
  }
  next();
});

// 封装请求时记录
window.axiosPendingRequests = [];
const source = CancelToken.source();
window.axiosPendingRequests.push(source);

axios.get('/api/data', {
  cancelToken: source.token
}).then(response => {
  // 请求完成后从数组中移除
  const index = window.axiosPendingRequests.indexOf(source);
  if (index > -1) {
    window.axiosPendingRequests.splice(index, 1);
  }
});

四、错误处理

取消请求会触发错误回调,需要通过 axios.isCancel 判断是否为取消操作:

axios.get('/user/12345', {
  cancelToken: source.token
}).catch(thrown => {
  if (axios.isCancel(thrown)) {
    console.log('Request canceled', thrown.message);
  } else {
    // 处理其他错误
    console.error(thrown);
  }
});

五、注意事项

  1. 取消请求后,Promise 会进入 rejected 状态,需要正确处理
  2. 在组件卸载或路由跳转时,建议取消所有 pending 请求
  3. 对于频繁触发的请求(如搜索建议),使用 CancelToken 可以有效避免竞态问题