登录
推荐 文章 Go 技术 课程 下载 专题 AI
首页 >  文章 >  前端

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 内置服务。

watch 回调、onWatcherCleanup 和失效标记属于同一轮侦听作用域。AbortController 通过 AbortSignal 与 fetch 请求关联。每轮分别创建控制器,能够避免共享变量被下一轮覆盖后,清理函数意外取消新请求。Vue 对清理注册的时机要求见侦听器清理文档。

Vue侦听回调清理函数与本轮控制器信号和请求资源的静态关系
静态结构图:每轮 watch 回调独立持有控制器和失效标记,清理函数关联本轮请求资源,不表示请求执行时间线。

结果、错误和加载状态共用有效性判断

这里的当前条件是 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 外面反复使用?

控制器与一次请求作用域对应更清楚。已经中止的信号不能重新变成未中止;共享可变引用还容易让旧轮清理误操作新轮资源。

声明:本文转载于:17golang原创 如有侵犯,请联系study_golang@163.com删除
相关阅读
更多>
最新阅读
更多>
课程推荐
更多>