Axios 取消请求的演进:CancelToken vs. AbortController

CancelToken(早期 Axios 取消方案)

Axios 早期使用 CancelToken 来实现请求取消,其核心是一个基于 Promise 的取消机制。我之前的文章有写

随着 Fetch API 的普及,浏览器引入了 AbortController,Axios 从 v0.22.0 开始支持该方案。下面就针对最新的AbortController做分享。

1. AbortController取消请求的基本原理

Axios 使用 AbortController 来实现请求取消功能。其核心机制是:

  1. 为每个请求创建一个 AbortController 实例
  2. 将控制器的 signal 附加到请求配置中
  3. 需要取消时调用控制器的 abort() 方法

2. 代码实现解析

2.1 请求拦截器设置

request.js 中,我们设置了请求拦截器来管理请求取消:

// 存储所有活动的 AbortController
const activeRequests = new Map();

request.interceptors.request.use((config) => {
  const requestId = generateRequestId(config);
  
  // 取消重复请求
  if (activeRequests.has(requestId)) {
    activeRequests.get(requestId).abort('取消重复请求');
  }
  
  // 创建并存储新的控制器
  const controller = new AbortController();
  activeRequests.set(requestId, controller);
  
  // 附加 signal 到请求
  config.signal = controller.signal;
  
  return config;
});

2.2 响应拦截器清理

请求完成后需要清理控制器:

request.interceptors.response.use((response) => {
  const requestId = generateRequestId(response.config);
  activeRequests.delete(requestId); // 清理控制器
  return response;
}, (error) => {
  // 错误处理
});

2.3 生成唯一请求ID

为了正确识别和取消特定请求,我们需要为每个请求生成唯一ID:

export function generateRequestId(config) {
  const params = JSON.stringify(config.params || {});
  const data = JSON.stringify(config.data || {});
  return `${config.url}-${config.method.toLowerCase()}-${params}-${data}`;
}

2.4 手动取消请求

提供手动取消请求的接口:

export function cancelRequest(requestId) {
  if (activeRequests.has(requestId)) {
    activeRequests.get(requestId).abort('用户手动取消');
    activeRequests.delete(requestId);
  }
}

3. 实际应用示例

3.1 流式请求中的取消

在 aiAgent.js 中,我们实现了流式请求的处理和取消:

export function qwenTalk(data, onProgress) {
    const config = {
        url: '/api/chat',
        method: 'POST',
        data,
        responseType: 'text'
    };
    currentRequestConfig = config;
    
    // 重置状态
    buffer = '';
    lastPosition = 0;
    
    return request({
        ...config,
        onDownloadProgress: (progressEvent) => {
            // 处理流数据
        },
    })
}

// 取消当前请求
export function cancelQwenTalk() {
    if (currentRequestConfig) {
        const requestId = generateRequestId(currentRequestConfig);
        cancelRequest(requestId);
        currentRequestConfig = null;
    }
}

3.2 使用示例

// 发起请求
const requestPromise = qwenTalk({message: 'Hello'}, (data) => {
    console.log('收到数据:', data);
});

// 取消请求
cancelQwenTalk();

4. 关键点总结

  1. 唯一请求标识:通过 URL、方法、参数和数据生成唯一ID,确保能准确识别请求
  2. 控制器管理:使用 Map 存储所有活动的 AbortController
  3. 自动取消:在请求拦截器中自动取消重复请求
  4. 手动取消:提供接口让开发者可以手动取消特定请求
  5. 资源清理:请求完成后及时清理控制器,避免内存泄漏
  6. 流式处理:对于流式请求,需要额外管理状态(如 buffer 和 position)

5. 最佳实践

  1. 对于耗时请求(如大文件上传/下载、流式响应)一定要实现取消功能
  2. 页面/组件卸载时取消所有未完成的请求
  3. 用户导航离开时考虑取消不必要的请求
  4. 对于表单提交等场景,可以防止重复提交

6. 注意事项

  1. 取消请求后,Axios 会抛出 CancelError,需要适当处理
  2. 确保请求ID生成逻辑覆盖所有可能影响请求唯一性的因素
  3. 在单页应用中,组件卸载时要清理相关请求

### 使用 AxiosCancelToken 取消请求 Axios 提供了一种机制,允许开发者通过 `CancelToken` 来取消正在进行的 HTTP 请求。以下是实现这一功能的具体方法: #### 创建 CancelToken 实例 可以通过调用 `axios.CancelToken.source()` 方法创建一个 cancel token 实例。该实例会返回两个属性:一个是用于触发取消操作的方法 (`cancel`),另一个是实际的 token。 ```javascript const CancelToken = axios.CancelToken; const source = CancelToken.source(); ``` #### 将 Token 添加到请求配置中 在发起请求时,将上述生成的 token 赋值给请求配置中的 `cancelToken` 属性[^1]。 ```javascript axios.get('/user/12345', { cancelToken: source.token }).catch(function (thrown) { if (axios.isCancel(thrown)) { console.log('Request canceled', thrown.message); } else { // 处理其他错误 } }); ``` #### 执行取消操作 当需要取消请求时,只需调用之前保存的 `source.cancel` 方法,并可传递一条消息作为参数。 ```javascript // 当某个条件满足时执行此函数 if (someCondition) { source.cancel('Operation canceled by the user.'); } ``` 以上代码展示了如何利用 Axios 的 `CancelToken` 功能安全有效地管理并终止不必要的网络请求。 #### 完整示例 下面是一个完整的例子,演示了如何结合按钮事件动态控制请求的启动与停止。 ```javascript function getUserAccount() { const CancelToken = axios.CancelToken; const source = CancelToken.source(); axios.get('/user/12345', { cancelToken: source.token }).then(response => { console.log(response.data); }).catch(thrown => { if (axios.isCancel(thrown)) { console.log('Request canceled', thrown.message); } else { throw thrown; // 或者处理其他类型的异常 } }); return () => { source.cancel('The operation was terminated.'); }; } let cancelRequest; document.getElementById('startButton').addEventListener('click', function(){ cancelRequest = getUserAccount(); }); document.getElementById('stopButton').addEventListener('click', function(){ if(cancelRequest){ cancelRequest(); } }); ```
评论 2
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值