Vue3 + Axios 请求中断实战:从CancelToken到AbortController的版本迁移指南

张开发
2026/4/16 13:32:49 15 分钟阅读

分享文章

Vue3 + Axios 请求中断实战:从CancelToken到AbortController的版本迁移指南
1. 为什么需要中断请求在实际开发中经常会遇到需要中断正在进行的HTTP请求的场景。想象一下这样的情形用户点击了一个按钮触发数据加载但在数据返回前又快速切换到了其他页面这时候如果不中断之前的请求不仅会浪费网络资源还可能导致数据混乱。我在项目中就遇到过这样的问题一个电商后台管理系统在商品列表页快速切换分类时由于没有处理请求中断导致最终展示的数据和当前分类不匹配。这种体验对用户来说非常糟糕。常见的需要中断请求的场景包括页面跳转时中断未完成的请求表单提交后用户快速重复点击提交按钮搜索框输入时中断上一次的搜索请求文件上传过程中用户取消上传轮询请求在组件销毁时需要中断2. 传统CancelToken方案在axios v0.22.0之前我们使用CancelToken来处理请求中断。这个方案基于一个被撤销的promise提案虽然现在已被弃用但很多老项目仍然在使用。2.1 CancelToken基本用法CancelToken的使用分为三个步骤创建cancel token的source在请求配置中传入token需要时调用cancel方法import axios from axios; // 创建source const source axios.CancelToken.source(); // 发送请求时配置cancelToken axios.get(/api/data, { cancelToken: source.token }).catch(err { if (axios.isCancel(err)) { console.log(请求被取消, err.message); } else { // 处理其他错误 } }); // 取消请求 source.cancel(操作被用户取消);2.2 在Vue3中的完整示例下面是一个在Vue3组件中使用CancelToken的完整例子template div button clickfetchData获取数据/button button clickcancelRequest取消请求/button /div /template script setup import { ref } from vue; import axios from axios; const source ref(null); const fetchData () { // 每次发起新请求前先取消之前的请求 if (source.value) { source.value.cancel(取消之前的请求); } source.value axios.CancelToken.source(); axios.get(/api/data, { cancelToken: source.value.token }).then(response { console.log(数据获取成功, response.data); }).catch(err { if (!axios.isCancel(err)) { console.error(请求出错, err); } }); }; const cancelRequest () { if (source.value) { source.value.cancel(用户手动取消请求); } }; /script2.3 CancelToken的工作原理CancelToken的实现原理很有意思创建一个pending状态的promise当调用cancel方法时resolve这个promiseaxios适配器检测到promise被resolve就会调用XMLHttpRequest的abort方法这种设计利用了promise的状态变化来触发请求中断是一种典型的观察者模式应用。3. 现代AbortController方案从axios v0.22.0开始推荐使用AbortController来中断请求。这是浏览器原生提供的API与fetch API的取消机制一致具有更好的兼容性和更简洁的语法。3.1 AbortController基本用法AbortController的使用更加直观const controller new AbortController(); axios.get(/api/data, { signal: controller.signal }).catch(err { if (err.name CanceledError) { console.log(请求被取消); } }); // 取消请求 controller.abort();3.2 在Vue3中的完整示例下面是Vue3组件中使用AbortController的例子template div button clickfetchData获取数据/button button clickcancelRequest取消请求/button /div /template script setup import { ref } from vue; import axios from axios; const controller ref(null); const fetchData () { // 取消之前的请求 if (controller.value) { controller.value.abort(); } controller.value new AbortController(); axios.get(/api/data, { signal: controller.value.signal }).then(response { console.log(数据获取成功, response.data); }).catch(err { if (err.name ! CanceledError) { console.error(请求出错, err); } }); }; const cancelRequest () { if (controller.value) { controller.value.abort(); } }; /script3.3 AbortController的优势相比CancelTokenAbortController有几个明显优势浏览器原生支持不需要额外实现可以与fetch API共用同一套中断逻辑语法更简洁直观更好的TypeScript类型支持更清晰的错误类型区分4. 版本迁移与兼容性处理如果你的项目需要从CancelToken迁移到AbortController需要考虑版本兼容性问题。我在实际迁移过程中总结了以下几点经验4.1 版本检查与升级首先检查项目中axios的版本npm list axios如果版本低于v0.22.0需要先升级npm install axioslatest4.2 渐进式迁移策略对于大型项目建议采用渐进式迁移新代码统一使用AbortController逐步重构旧代码中的CancelToken对于共享的请求封装层可以同时支持两种方案4.3 混合使用方案在过渡期间你甚至可以同时使用两种方案const controller new AbortController(); const source axios.CancelToken.source(); axios.get(/api/data, { cancelToken: source.token, signal: controller.signal }).catch(err { if (axios.isCancel(err)) { console.log(CancelToken取消); } else if (err.name CanceledError) { console.log(AbortController取消); } }); // 两种方式都可以取消请求 source.cancel(); // 或 controller.abort();4.4 常见问题解决在迁移过程中可能会遇到以下问题TypeScript类型错误确保安装了最新版本的axios类型定义测试用例失败更新测试中对取消请求的断言第三方库兼容性检查项目中其他库是否依赖特定版本的axios5. 实战技巧与最佳实践在实际项目中我们通常不会直接使用这些基础API而是会进行封装。下面分享几个我在项目中总结的实用技巧。5.1 请求去重封装对于频繁触发的请求如搜索框输入可以使用AbortController实现自动去重const pendingRequests new Map(); function getRequestKey(config) { return ${config.method}-${config.url}-${JSON.stringify(config.params)}; } function addPendingRequest(config) { const key getRequestKey(config); const controller new AbortController(); config.signal controller.signal; if (pendingRequests.has(key)) { pendingRequests.get(key).abort(); } pendingRequests.set(key, controller); } function removePendingRequest(config) { const key getRequestKey(config); if (pendingRequests.has(key)) { pendingRequests.delete(key); } } // 在axios拦截器中使用 axios.interceptors.request.use(config { removePendingRequest(config); addPendingRequest(config); return config; }); axios.interceptors.response.use(response { removePendingRequest(response.config); return response; }, error { removePendingRequest(error.config); return Promise.reject(error); });5.2 路由切换时取消请求在Vue Router中可以在导航守卫中取消所有pending的请求import { createRouter } from vue-router; const router createRouter({ // ...路由配置 }); const pendingRequests new Set(); axios.interceptors.request.use(config { const controller new AbortController(); config.signal controller.signal; pendingRequests.add(controller); return config; }); axios.interceptors.response.use(response { pendingRequests.delete(response.config.signal); return response; }, error { pendingRequests.delete(error.config.signal); return Promise.reject(error); }); router.beforeEach(() { pendingRequests.forEach(controller controller.abort()); pendingRequests.clear(); });5.3 与Composition API结合在Vue3的setup函数中我们可以创建更优雅的封装import { onUnmounted } from vue; export function useAxios() { const controllers new Set(); const request async (config) { const controller new AbortController(); controllers.add(controller); try { const response await axios({ ...config, signal: controller.signal }); return response; } finally { controllers.delete(controller); } }; const cancelAll () { controllers.forEach(c c.abort()); controllers.clear(); }; onUnmounted(() { cancelAll(); }); return { request, cancelAll }; }6. 错误处理与调试技巧正确处理请求中断相关的错误对于应用稳定性至关重要。我在项目中遇到过各种边界情况这里分享一些经验。6.1 区分错误类型无论是CancelToken还是AbortController都需要正确区分请求中断错误和其他类型的错误axios.get(/api/data, { signal: controller.signal }).catch(err { if (axios.isCancel(err)) { // CancelToken的中断错误 console.log(请求被CancelToken取消, err.message); } else if (err.name CanceledError) { // AbortController的中断错误 console.log(请求被AbortController取消); } else { // 其他类型的错误 console.error(请求出错, err); } });6.2 调试技巧当请求中断不生效时可以按照以下步骤排查检查axios版本是否支持AbortController确认signal或cancelToken是否正确传递到了请求配置使用浏览器开发者工具查看网络请求是否真的被取消检查是否有其他拦截器修改了请求配置6.3 性能考虑频繁创建和取消请求可能会影响性能特别是在低端移动设备上。对于高频触发的操作如搜索框输入建议配合防抖使用import { debounce } from lodash-es; const search debounce(async (query) { try { const response await axios.get(/api/search, { params: { q: query }, signal: controller.signal }); // 处理结果 } catch (err) { if (err.name ! CanceledError) { console.error(err); } } }, 300);7. 测试策略为请求中断逻辑编写测试用例可以避免很多潜在问题。下面介绍几种测试方案。7.1 单元测试示例使用jest测试请求中断import axios from axios; import { test, expect } from vitest; test(应该能用AbortController取消请求, async () { const controller new AbortController(); const promise axios.get(https://example.com/api, { signal: controller.signal }); controller.abort(); await expect(promise).rejects.toHaveProperty(name, CanceledError); }); test(应该能用CancelToken取消请求, async () { const source axios.CancelToken.source(); const promise axios.get(https://example.com/api, { cancelToken: source.token }); source.cancel(测试取消); await expect(promise).rejects.toMatchObject({ message: 测试取消, __CANCEL__: true }); });7.2 E2E测试建议对于完整的用户场景可以使用Cypress进行测试describe(请求中断, () { it(应该在页面离开时取消请求, () { cy.intercept(GET, /api/data).as(dataRequest); cy.visit(/page-with-request); cy.wait(dataRequest); cy.get(dataRequest).should(have.property, state, Complete); cy.intercept(GET, /api/data, { delay: 1000 }).as(slowRequest); cy.visit(/page-with-request); cy.go(back); cy.get(slowRequest).should(have.property, state, Cancelled); }); });7.3 测试注意事项模拟网络延迟以便观察中断效果测试中断后是否触发了正确的错误处理逻辑验证内存泄漏问题确保被取消的请求不会保留引用测试并发请求的中断行为8. 总结与个人经验分享在长期使用axios进行开发的过程中我深刻体会到正确管理请求生命周期的重要性。特别是在复杂的单页应用中不当的请求管理会导致内存泄漏、数据不一致等各种问题。从CancelToken迁移到AbortController的过程相对平滑但需要注意版本兼容性。对于新项目我强烈建议直接使用AbortController它不仅更符合现代Web标准而且在TypeScript支持和使用体验上都更优秀。一个常见的误区是在组件卸载时忘记取消请求。在Vue3中我习惯在onUnmounted钩子中自动取消所有pending请求这是一个很好的实践。另外对于重要的全局请求如用户信息、权限数据等可以设置标志位避免被意外取消。在实际项目中我会根据业务场景选择不同的中断策略。对于表单提交这类关键操作可能只需要防止重复提交而对于数据列表、搜索这类场景则需要更积极的请求中断策略来保证数据一致性。

更多文章