Vue onWatcherCleanup 怎样取消过期请求
来源:17golang原创
时间:2026-11-07 00:22:12 232浏览 收藏
Vue 3.5+ 可以在 watch 回调的同步部分调用 onWatcherCleanup,注册一个清理函数,在该轮侦听失效时调用 AbortController.abort() 取消仍在进行的 fetch 请求。关键是每轮创建独立控制器,并在第一个 await 前完成注册。页面状态还应判断本轮是否有效,避免旧请求的结果、错误或 finally 覆盖新状态。
官方地址:https://vuejs.org/
设想搜索框先查询“书架”,随后很快改成“书桌”。后一个请求先返回,前一个却在稍后完成。如果两个回调都直接给列表赋值,页面就可能显示旧关键词的结果。下面把这个现象作为示例场景,拆清根因和处理方式,不把它描述成某次真实线上事故或实测记录。
旧请求覆盖新结果,问题在状态归属
异步响应的返回顺序并不保证与输入顺序一致。侦听器重新执行,也不会自动替你取消前一轮创建的所有异步资源。修复需要同时处理两件事:请求不再需要时取消它;异步回调准备修改页面时,确认它仍属于当前查询。
只给结果加判断仍不够。例如旧请求的 finally 无条件把 loading 设成 false,会让新请求尚未完成时加载提示就消失。错误状态也一样:过期请求的失败信息不应该盖住新结果。因此下面统一用 isCurrent() 判断三类状态。
每轮请求拥有自己的取消控制器
以下是一个 Vue 3.5+ 单文件组件示例。需要项目自行提供同源的 /api/search?q=关键词,并返回包含字符串数组 items 的 JSON;这个接口是应用约定,不是 Vue 内置服务。
正在查询
{{ error }}
- {{ item }}
watch 回调、onWatcherCleanup 和失效标记属于同一轮侦听作用域。AbortController 通过 AbortSignal 与 fetch 请求关联。每轮分别创建控制器,能够避免共享变量被下一轮覆盖后,清理函数意外取消新请求。Vue 对清理注册的时机要求见侦听器清理文档。

结果、错误和加载状态共用有效性判断
这里的当前条件是 query 规范化后的值,本轮条件是回调参数 term,有效标记是 active。isCurrent() 把三者组合成有效性判断,再约束结果状态 items、错误状态 error 和加载状态 loading。这个判断是示例的状态提交规则,和浏览器是否已经结束网络连接是两回事。
cleanup 设置 active 为 false,随后 abort 请求;catch 看到主动取消或过期状态时直接退出。但 JavaScript 仍会执行 finally,所以 finally 也必须保留判断。当前条件比较还避免了条件已经变更、旧回调却准备提交状态时,把旧内容写回页面。

| 异步位置 | 未经保护的影响 | 示例处理 |
|---|---|---|
| 成功响应 | 旧列表覆盖新列表 | 有效时才设置 items |
| catch | 取消被误报为业务失败 | 取消或过期时忽略 |
| finally | 旧请求关掉新 loading | 有效时才结束加载 |
浏览器 abort 能取消未完成的 fetch、响应体读取等操作,见AbortController.abort 文档。它不能撤销已经提交的页面赋值,也不能作为服务端业务回滚机制。对于不接受 signal 的请求库,应使用其取消接口,并继续保留有效性判断。
清理必须在 await 前注册,旧项目可用 onCleanup
onWatcherCleanup API要求在侦听器的同步执行期间调用。不要先 await 请求或异步准备工作,再补注册。那时侦听器上下文可能已经不在,而且请求也可能早已失效。
Vue 3.5 以前的 Vue 3 项目,可以使用 watch 回调第三个参数 onCleanup。以下片段只展示注册方式,省略的请求状态逻辑应沿用上面的 active 和 isCurrent() 保护;不要在同一个回调里重复注册两套相同清理。
// 沿用已有query;第三个参数与当前侦听器实例绑定
watch(() => query.value.trim(), (term, _oldTerm, onCleanup) => {
if (!term) return
const controller = new AbortController()
// 取消注册仍建议紧挨资源创建,避免异步窗口
onCleanup(() => controller.abort())
// 这里只演示请求取消,不向页面提交结果
fetch('/api/search?q=' + encodeURIComponent(term), {
signal: controller.signal,
}).catch(cause => {
if (!controller.signal.aborted) console.error('请求失败', cause)
})
})
参数形式的 onCleanup 与全局导入函数的限制不同,但资源创建后及时登记仍然更容易维护。不要仅为了让注册不报错而吞掉上下文错误;先确认当前 Vue 版本和注册位置。
用乱序响应场景确认状态属于当前查询
接入已有接口后,可以让读者在开发环境中让第一轮查询响应较慢,再快速切换到第二轮。预期是旧请求被取消或旧回调不再提交状态,第二轮数据不会被旧列表覆盖;如果第二轮仍在等待,旧请求的 finally 不应关闭 loading。再观察 HTTP 错误状态、非法 JSON 或错误 items 格式是否只影响当前查询。
这些是示例的预期行为与检查方法,本文没有执行请求或生成运行截图。fetch 对 HTTP 状态的处理依据见Fetch 使用说明。
取消请求是否等于防抖?
不等于。取消处理已经发出的过期请求,防抖减少开始请求的频率。需要减少查询次数时,可以另行设计防抖,但它不能代替状态归属判断。
组件卸载后还要手动再 abort 一次吗?
同步在 script setup 中创建的侦听器会随组件卸载停止,其清理负责释放本轮资源。如果在异步回调里创建侦听器,需要自行管理停止句柄,不能直接套用组件自动绑定的结论。
为什么不把一个 controller 放在 watch 外面反复使用?
控制器与一次请求作用域对应更清楚。已经中止的信号不能重新变成未中止;共享可变引用还容易让旧轮清理误操作新轮资源。
-
502 收藏
-
501 收藏
-
501 收藏
-
501 收藏
-
501 收藏
-
437 收藏
-
488 收藏
-
343 收藏
-
404 收藏
-
390 收藏
-
102 收藏
-
147 收藏
-
186 收藏
-
431 收藏
-
139 收藏
-
361 收藏
-
420 收藏
-
- 前端进阶之JavaScript设计模式
- 设计模式是开发人员在软件开发过程中面临一般问题时的解决方案,代表了最佳的实践。本课程的主打内容包括JS常见设计模式以及具体应用场景,打造一站式知识长龙服务,适合有JS基础的同学学习。
- 立即学习 543次学习
-
- GO语言核心编程课程
- 本课程采用真实案例,全面具体可落地,从理论到实践,一步一步将GO核心编程技术、编程思想、底层实现融会贯通,使学习者贴近时代脉搏,做IT互联网时代的弄潮儿。
- 立即学习 516次学习
-
- 简单聊聊mysql8与网络通信
- 如有问题加微信:Le-studyg;在课程中,我们将首先介绍MySQL8的新特性,包括性能优化、安全增强、新数据类型等,帮助学生快速熟悉MySQL8的最新功能。接着,我们将深入解析MySQL的网络通信机制,包括协议、连接管理、数据传输等,让
- 立即学习 500次学习
-
- JavaScript正则表达式基础与实战
- 在任何一门编程语言中,正则表达式,都是一项重要的知识,它提供了高效的字符串匹配与捕获机制,可以极大的简化程序设计。
- 立即学习 487次学习
-
- 从零制作响应式网站—Grid布局
- 本系列教程将展示从零制作一个假想的网络科技公司官网,分为导航,轮播,关于我们,成功案例,服务流程,团队介绍,数据部分,公司动态,底部信息等内容区块。网站整体采用CSSGrid布局,支持响应式,有流畅过渡和展现动画。
- 立即学习 485次学习