前端进阶之旅前端进阶之旅
  • 基础篇HTML/CSS/JS 打底
  • 进阶篇原理与工程化
  • 高频篇面试最常问的那批
  • 精选篇按模块收敛的总结
  • 手写篇常考代码手写实现
  • 面经篇真实面试问题复盘
  • AI 篇NEWAI 时代的前端考点
  • 历年面经NEW按年份追踪真实考点
  • 每日一题每天一道,攒手感
  • 专项自测100 题快速查漏
  • 小程序题库小程序专项刷题
  • 算法题库NEW在线编码即时判题
  • 知识卡片NEW碎片时间过考点
  • 面试题大全常见问题解析
  • AI 答疑NEW随时提问,即时解析
  • AI 模拟面试NEW模拟真实面试 + 报告
  • AI 定制路线NEW按你的简历现排
  • AI 知识地图NEW串起全站知识点
  • 原理篇React / Vue 源码拆解
  • HTTP从报文一路讲到 HTTPS
  • 浏览器渲染、事件循环、进程
  • 计算机基础Linux、网络、操作系统
  • 设计模式23 种模式怎么用
  • Node学习指南从环境搭建到服务端
  • NPM工作流script、依赖与发布
  • Docker容器化部署上手
  • Canvas图形与动画实战
  • 前端系统进阶学习大型项目工程化
  • 前端综合文章长期沉淀的实践文
  • 思维导图知识点全景图
  • 学习路线按图索骥不跑偏
  • AI 热点NEWAI 每日动态
  • 公众号动态公众号历史文章
  • 博客动态站长的技术博客
  • 开发者导航常用工具与文档站
  • 基础篇HTML/CSS/JS 打底
  • 进阶篇原理与工程化
  • 高频篇面试最常问的那批
  • 精选篇按模块收敛的总结
  • 手写篇常考代码手写实现
  • 面经篇真实面试问题复盘
  • AI 篇NEWAI 时代的前端考点
  • 历年面经NEW按年份追踪真实考点
  • 每日一题每天一道,攒手感
  • 专项自测100 题快速查漏
  • 小程序题库小程序专项刷题
  • 算法题库NEW在线编码即时判题
  • 知识卡片NEW碎片时间过考点
  • 面试题大全常见问题解析
  • AI 答疑NEW随时提问,即时解析
  • AI 模拟面试NEW模拟真实面试 + 报告
  • AI 定制路线NEW按你的简历现排
  • AI 知识地图NEW串起全站知识点
  • 原理篇React / Vue 源码拆解
  • HTTP从报文一路讲到 HTTPS
  • 浏览器渲染、事件循环、进程
  • 计算机基础Linux、网络、操作系统
  • 设计模式23 种模式怎么用
  • Node学习指南从环境搭建到服务端
  • NPM工作流script、依赖与发布
  • Docker容器化部署上手
  • Canvas图形与动画实战
  • 前端系统进阶学习大型项目工程化
  • 前端综合文章长期沉淀的实践文
  • 思维导图知识点全景图
  • 学习路线按图索骥不跑偏
  • AI 热点NEWAI 每日动态
  • 公众号动态公众号历史文章
  • 博客动态站长的技术博客
  • 开发者导航常用工具与文档站
首页程序员面试题库Vue watch 竞态 过期请求 副作用清理
VuVue侦听与生命周期

Vue watch 发请求时,如何避免旧响应覆盖新结果?

请求按发起顺序开始,却可能按相反顺序完成;最后返回的不一定是用户现在要的结果。

前端进阶之旅 · 一题精讲更新于 2026.09.06
Vue#侦听与生命周期
先看核心答案读代码示例
理解线索

按请求轮次管理提交资格

  1. 启动本轮记录本次源值并建立独立取消信号
  2. 清理旧轮标记失效并尝试取消仍在进行的操作
  3. 提交结果只有仍然有效的轮次可以写入界面

取消客户端请求不等于撤销服务端已经执行的写操作,本方案针对读取结果的界面一致性。

核心回答

先记住这个答案

为 watch 每一轮请求建立独立有效标记,并在清理回调中使旧轮失效;若请求接口支持 AbortSignal,再同时取消旧请求以节省工作。所有异步状态提交都要检查本轮是否仍然有效,包括成功数据、错误提示和 loading。不能只保护成功分支,因为旧请求的 catch 或 finally 也可能覆盖当前请求的界面。

  • 清理让上一轮失去状态提交资格
  • 取消请求与拒绝旧结果各有职责
  • 成功、失败、结束状态都需要守卫

竞态来自完成顺序不受控制

用户先选择甲再选择乙,乙的数据先返回并显示,随后甲的慢响应到达。如果两个回调都直接赋值,界面会退回甲的数据。源已经变成乙也不会自动阻止甲回调里的普通赋值。

为了复现,应使用可控制完成顺序的请求替身,先完成新请求再完成旧请求。单靠真实网络偶发延迟难以稳定验证,也容易漏掉旧请求失败后覆盖当前错误提示的另一条路径。

把失效和取消放进同一轮清理

下例返回一个可在组件同步初始化期调用的组合式函数,加载器由调用方传入。清理在开始等待之前就注册,使请求尚未完成时源切换或监听停止也能够使该轮失效。

默认侦听器会批量调度;若业务要求源赋值后立刻取消,就要另行设计同步失效策略。这里保护的是新一轮侦听器运行后旧任务不得再提交,并未声称改变源变量的瞬间所有 Promise 都被中断。

同时保护 data、error 和 loadingTypeScript
import { ref, watch } from 'vue'
import type { Ref } from 'vue'

function useLatestResult(id: Ref<string>, load: (id: string, signal: AbortSignal) => Promise<string>) {
  const data = ref<string | null>(null)
  const error = ref<string | null>(null)
  const loading = ref(false)
  const stop = watch(id, async (key, _old, onCleanup) => {
    let active = true
    const controller = new AbortController()
    onCleanup(() => { active = false; controller.abort() })
    data.value = null
    error.value = null
    loading.value = true
    try {
      const result = await load(key, controller.signal)
      if (active) data.value = result
    } catch (cause) {
      if (active) error.value = cause instanceof Error ? cause.message : String(cause)
    } finally {
      if (active) loading.value = false
    }
  }, { immediate: true })
  return { data, error, loading, stop }
}

加载器即便忽略取消信号并最终返回,active 守卫也会挡住旧结果。停止监听后本例不主动重置可见 loading,因为停止后的界面保留还是清空属于调用方的结束流程。

取消不能取代提交校验

有些缓存读取、第三方 SDK 或后处理步骤不支持取消,甚至请求已成功但解析仍未完成。有效标记保护的是最终写入位置,因此应保留到所有异步转换结束,而不是仅检查 fetch 是否取消成功。

对提交订单之类写请求,应使用显式用户动作及服务端幂等设计。把写请求塞进自动重跑的侦听器,再寄希望于取消旧请求来防止重复提交,会混淆界面更新和业务副作用的执行保证。

回答前,多想一步

容易答错的地方

只给成功赋值加判断
旧任务的 finally 仍会把新任务的 loading 设为假,旧异常也可能盖住新结果。检查回调所有状态写入点,让错误与完成分支使用同一轮资格判断,而不是各自维护不一致的标记。
认为发起取消就必然没有旧回调
取消是否生效取决于具体接口和任务所处阶段。即便 Promise 以异常结束,catch 与 finally 仍会执行;应把取消当成资源优化,把有效标记当成状态一致性的最后一道条件。
试着用自己的话回答

面试官还会怎么问?

为什么每一轮都需要自己的 active 变量?

共享一个布尔值会被新一轮重新设为真,旧回调随后可能误以为自己仍然有效。闭包里的局部标记只属于本轮,旧轮清理后永远不会被后来的请求重新启用。

序号比较能替代布尔有效标记吗?

可以用递增请求编号,在提交时比较当前编号。不过停止监听和组件退出也必须推进失效状态,否则最后一个尚未完成的请求仍可能通过编号检查;清理回调更方便统一这些结束路径。

如何验证旧 finally 不影响当前 loading?

让旧请求保持未完成,启动新请求后先让旧请求结束,此时新请求仍在等待,loading 应继续为真。随后完成新请求才应变为假,这比只验证最终 data 更能覆盖实际界面错误。

从一道题,走向一组知识

把知识连起来

侦听与生命周期

Vue 3.5 onWatcherCleanup 和 onCleanup 有什么区别,能在 await 后调用吗?

进一步区分两种清理注册 API 的调用时机与版本条件。

应用与性能

Vue 组合式函数封装异步请求时,如何设计 loading、error、data 和竞态保护?

把请求状态封装为组合式函数时,还要确定返回接口和重置策略。

参考资料

  • Vue:副作用清理
  • MDN:AbortController

示例用于理解所注明的运行环境与边界;延伸学习可结合原文中的更多案例。

本题目录
  1. 先记住这个答案
  2. 竞态来自完成顺序不受控制
  3. 把失效和取消放进同一轮清理
  4. 取消不能取代提交校验
  5. 容易答错的地方
  6. 面试官还会怎么问
  7. 把知识连起来
读懂,再试着讲出来

先看核心答案,再读代码。最后展开追问,检查自己有没有遗漏边界。

试着回答追问
浏览全部面试题理解原理,也关注真实的使用场景。回到顶部 ↑