前端进阶之旅前端进阶之旅
  • 基础篇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 异步组合式函数 loading error data 请求竞态
VuVue应用与性能

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

三个状态字段只是表面,关键是保证写入它们的结果仍属于当前那次请求。

前端进阶之旅 · 一题精讲更新于 2026.09.06
Vue#应用与性能#异步编程#安全#性能优化
先看核心答案读代码示例
理解线索

一次请求有清楚的生命周期

  1. 开始确定请求键并设置本次加载状态
  2. 结束只有仍有效的请求能写入结果或错误
  3. 失效参数改变或作用域结束后停止使用旧结果

下面示例采用开始新请求时清空旧数据的策略,不包含缓存、自动重试或 SSR 数据注水。

核心回答

先记住这个答案

异步组合式函数应先约定请求开始、成功、失败和失效时的状态转换,再为每次请求保留独立身份或有效标记。参数变化时清理旧请求,既尝试取消外部工作,也阻止晚到结果覆盖当前 data、error 和 loading。旧请求的 finally 同样需要检查有效性,否则可能提前关闭新请求的加载状态。是否保留旧数据、怎样显示刷新失败,以及空参数是否请求,都应写进接口契约。

  • data、error、loading 必须属于同一当前请求语境
  • 取消请求与忽略过期结果是互补措施
  • 成功、失败、finally 和卸载清理都需要考虑竞态

先约定界面状态的含义

搜索列表更新关键词时,可以清空旧结果,也可以保留旧数据并显示刷新中,两种体验都合理,但 data 当前属于哪个请求必须清楚。错误出现后是否保留旧结果,也应有明确规则。不要用 data 是否为真来推断是否成功,因为空数组或合法空值可能就是成功结果。

下面使用可注入的加载函数,方便在测试中控制完成顺序,而不是固定连接某个后端。每次 watch 执行先建立有效标记和清理函数,空键不发请求;新请求开始时清空旧数据和错误,保持状态转换可预测。

取消旧工作并拒绝过期结果写入TypeScript
import { ref, shallowRef, toValue, watch } from 'vue';
import type { MaybeRefOrGetter } from 'vue';

function useRemote<T>(source: MaybeRefOrGetter<string>,
  load: (key: string, signal: AbortSignal) => Promise<T>) {
  const data = shallowRef<T | null>(null);
  const error = shallowRef<Error | null>(null);
  const loading = ref(false);
  watch(() => toValue(source), async (key, _, onCleanup) => {
    let active = true;
    const controller = new AbortController();
    onCleanup(() => {
      active = false;
      controller.abort();
      loading.value = false;
    });
    data.value = null;
    error.value = null;
    if (!key) { loading.value = false; return; }
    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 : new Error(String(cause));
    } finally {
      if (active) loading.value = false;
    }
  }, { immediate: true });
  return { data, error, loading };
}

在组件 setup 的同步调用链或明确管理的 effectScope 中使用。加载器可以响应 signal 取消,也可能忽略取消;active 标记仍会阻止旧结果覆盖新请求。

为什么只调用 abort 还不够

取消能否及时生效取决于底层工作,结果也可能已经进入后续处理。因此应让写入操作同时检查当前请求是否有效。旧请求失败时不能覆盖新请求的错误状态,旧 finally 也不能把新请求仍在进行的 loading 改成 false。

测试可以让请求 A 先启动、B 后启动却先完成,再让 A 晚到,确认最终仍保留 B 的数据。还应反过来让旧请求先结束而新请求未完成,检查加载状态不会提前结束。这些顺序比只测试一次正常成功更能说明实现是否可靠。

清理、错误与 SSR 仍有边界

在组件或效果作用域结束时,watch 的清理会使当前请求失效并尝试取消。若在没有明确作用域的任意异步回调里创建 watcher,就需要自己管理停止行为。加载器还应按后端协议处理非成功状态与数据解析,组合式函数不会自动判断返回内容是否符合业务结构。

客户端封装不等于完整 SSR 数据方案。服务端首屏取数、缓存键和客户端避免重复请求,需要与所用框架的数据机制协调。若加入重试或手动刷新,还要明确它们是否产生新请求身份,不能绕过同一套状态与失效规则。

回答前,多想一步

容易答错的地方

只给 data 写入加有效性检查
error 和 finally 也可能来自过期请求,必须一起约束,才能保持状态一致。排查“Vue 异步组合式函数 loading error data 请求竞态”时还要核对输入、版本和执行顺序,并用反例确认修复后的边界。
用了 AbortController 就保证旧任务绝不完成
底层实现可能忽略取消或已进入后续阶段,仍需要拒绝过期结果的逻辑。排查“Vue 异步组合式函数 loading error data 请求竞态”时还要核对输入、版本和执行顺序,并用反例确认修复后的边界。
试着用自己的话回答

面试官还会怎么问?

新请求开始一定要清空旧数据吗?

不是,保留旧数据适合刷新体验,但要同时记录当前请求键和刷新状态,避免让用户误认数据属于新条件。

为什么把 load 作为参数传入?

可以分离网络协议与状态管理,也方便用可控 Promise 测试竞态和错误,不必依赖真实网络速度。

loading 能表示所有异步阶段吗?

简单请求可以,复杂流程可能还需区分初次加载、刷新、取消或重试;状态命名应反映实际用户需要。

从一道题,走向一组知识

把知识连起来

应用与性能

Vue 组合式函数怎样同时接收普通值、ref 和 getter 并保持追踪?

用正确来源形式追踪请求键变化继续对照“Vue 异步组合式函数 loading error data 请求竞态”的触发条件、结果与失败边界。

应用与性能

Vue 组合式函数里的生命周期钩子,为什么通常要在 setup 同步注册?

确保 watcher 与清理拥有正确作用域

应用与性能

Vue 组合式函数怎样独立测试,什么时候仍需要挂载组件?

控制 Promise 完成顺序验证竞态

参考资料

  • Vue:组合式函数异步状态
  • Vue:侦听器副作用清理

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

本题目录
  1. 先记住这个答案
  2. 先约定界面状态的含义
  3. 为什么只调用 abort 还不够
  4. 清理、错误与 SSR 仍有边界
  5. 容易答错的地方
  6. 面试官还会怎么问
  7. 把知识连起来
读懂,再试着讲出来

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

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