☰
VueUse useTimeoutPoll 详解:等待异步任务完成后再轮询的定时器方案(airi 项目实践)
2026/10/15 18:26:10 网站建设 项目流程
  • 人工智能
  • AI Agent
  • MCP 服务
  • 工具调用
  • GUI 自动化
  • 语音
  • 数字人
  • 交互助手

【免费下载链接】airi

💖🧸 自托管、归你拥有的 Grok 风格 AI 伴侣与 waifu / 赛博生命灵魂容器,目标是接近 Neuro-sama 的高度;支持实时语音聊天、Minecraft 和 Factorio 游玩,支持 Web / macOS / Windows。

项目地址:https://gitcode.com/moeru-ai/airi
点击查看免费下载

useTimeoutPoll是 VueUse 提供的一个定时轮询组合式函数,其核心语义是:每一次轮询回调执行完毕后,才重新计时调度下一次执行,从而天然避免setInterval在回调为异步任务时出现的"任务堆积"与"重叠执行"问题。在 airi 这类大量使用 Vue 3 组合式 API、并且依赖@vueuse/core的 Monorepo 项目中(锁定版本@vueuse/core@14.4.0,见 pnpm-lock.yaml),它适合用于状态同步、配置刷新、轻量级心跳等场景。读完本文,你将掌握useTimeoutPoll的完整 API、配置项语义、与useIntervalFn/useTimeoutFn的取舍,以及如何在真实组件中落地可暂停、可恢复的轮询逻辑。

为什么需要 useTimeoutPoll

传统的setInterval(fn, delay)有一个经典陷阱:如果fn是耗时的异步任务(如发起网络请求),任务的实际执行时间可能超过delay,导致下一次回调在上一次任务尚未完成时就被触发,出现并发重叠、状态错乱甚至请求风暴。

useTimeoutPoll的文档给出了明确的核心语义:

Use timeout to poll something. It will trigger callback after last task is done.

即"在最后一次任务完成之后,才触发下一次回调"。它在内部基于setTimeout而非setInterval调度:每次调用传入的fn(无论同步还是异步),等到fn返回的 Promise 完成(settle)之后,再重新安排interval毫秒后的下一次执行。这样任意时刻最多只有一个轮询任务在飞行中,天然保证了串行与安全。

在 airi 仓库中,同样可以看到手动setInterval轮询带来的样板代码,例如 apps/component-calling/src/utils/xsai-testing.ts 中const interval = setInterval(() => {...})的手写写法——需要自行管理句柄、清理与暂停恢复。这正是useTimeoutPoll这类组合式函数要消除的重复劳动。

基本用法

从@vueuse/core引入后,只需传入回调函数与轮询间隔:

import { useTimeoutPoll } from '@vueuse/core' import { ref } from 'vue' const count = ref(0) async function fetchData() { await new Promise(resolve => setTimeout(resolve, 1000)) count.value++ } // 只有上一次 fetchData 完成之后,才会开始下一次 1000ms 计时 const { isActive, pause, resume } = useTimeoutPoll(fetchData, 1000)

在这个示例中:

  • fetchData是一个耗时约 1 秒的异步任务;
  • 轮询间隔为1000ms;
  • 由于useTimeoutPoll等待上一次任务完成后才重新计时,实际触发节奏约为"任务耗时 + 间隔",即大约每 2 秒一次,而不是固定 1 秒一次;
  • 解构出的isActive、pause、resume提供了对轮询生命周期的完全控制(对应Pausable返回类型,下详)。

注意:回调参数的类型是fn: () => Awaitable<void>,即同步返回void或返回 Promise 的函数均可;间隔参数interval: MaybeRefOrGetter<number>,意味着它可以是普通数字、ref或 getter 函数(详见下文"动态间隔")。

完整类型声明与配置项

关联文档给出了完整的类型声明,这是理解全部行为的钥匙:

export interface UseTimeoutPollOptions { /** * 是否立即启动计时器 * * @default true */ immediate?: boolean /** * 调用 resume 后是否立即执行回调 * * @default false */ immediateCallback?: boolean } export declare function useTimeoutPoll( fn: () => Awaitable<void>, interval: MaybeRefOrGetter<number>, options?: UseTimeoutFnOptions, ): Pausable

参数说明

参数类型说明
fn() => Awaitable<void>每次轮询要执行的回调,可以是同步函数或返回 Promise 的异步函数
intervalMaybeRefOrGetter<number>轮询间隔(毫秒)。支持普通数字、ref<number>或() => numbergetter,便于动态调整
optionsUseTimeoutPollOptions可选配置,见下表

配置项说明

配置项默认值含义
immediatetrue是否在创建组合式函数后立即启动计时器。设为false后,轮询默认处于暂停状态,需要手动调用resume()才开始
immediateCallbackfalse调用resume()恢复轮询时,是否立即执行一次回调,而不是等待下一个间隔。适合"暂停后恢复时要立刻刷新一次最新状态"的场景

返回的 Pausable 接口

函数返回类型为Pausable,这是一个在 VueUse 计时器家族中广泛复用的接口(useIntervalFn的UseIntervalFnReturn同样定义为Pausable,见 references/useIntervalFn.md),包含三个成员:

  • isActive: Ref<boolean>:当前轮询是否处于活跃(运行中)状态,可直接用于模板或watch;
  • pause(): void:暂停轮询。下一次已调度的setTimeout会被清除,且已暂停状态被持久保留;
  • resume(): void:恢复轮询。默认等待一个完整间隔后再触发,若配置immediateCallback: true则会立即执行一次回调。

与 useIntervalFn、useTimeoutFn 的取舍

在 airi 的 VueUse 技能知识库中,这三个函数分属不同定位(完整目录见 .agents/skills/vueuse-functions/SKILL.md):

函数类别调度方式是否等待异步回调完成适用场景
useIntervalFnAnimationsetInterval否高频、确定性的轻量任务(如动画、定时累加)
useTimeoutFnAnimationsetTimeout(一次性)是(一次任务)延迟执行单次任务,返回Stoppable
useTimeoutPollUtilitiessetTimeout(循环)是轮询远端状态、异步刷新、心跳

三者共享immediate/immediateCallback两个配置项(useTimeoutFn对应start/stop与Stoppable返回,见 references/useTimeoutFn.md)。选择建议:

  • 回调是纯同步且很快的(如本地计数器),useIntervalFn足够且开销更低;
  • 回调发起网络请求、读写存储等异步操作,务必选择useTimeoutPoll,避免重叠;
  • 只需执行一次延迟任务,用useTimeoutFn。

进阶实战:动态间隔与状态刷新

场景一:动态轮询间隔

由于interval接受MaybeRefOrGetter<number>,可以依据某个状态实时改变轮询频率。例如在 AI 伴侣类应用中,根据"是否处于活跃对话"决定状态刷新频率:

import { useTimeoutPoll } from '@vueuse/core' import { computed, ref } from 'vue' const isInConversation = ref(false) const intervalGetter = computed(() => (isInConversation.value ? 2000 : 15000)) const { isActive, pause, resume } = useTimeoutPoll( async () => { // 例如拉取最新状态快照 await refreshSnapshot() }, intervalGetter, )

间隔变化会在下一次调度时自然生效,无需手动重启轮询。

场景二:暂停后立即刷新

当用户重新回到页面或窗口获得焦点时,通常希望立刻获得最新状态而不是干等一个间隔:

const { resume } = useTimeoutPoll(fetchStatus, 5000, { immediate: false, immediateCallback: true, // resume 后立即执行一次 fetchStatus }) onFocus(() => resume()) onBlur(() => pause())

场景三:与 useFetch 等异步工具协同

fn支持 async 函数,因此可以自然地包裹任意异步逻辑:

import { useFetch, useTimeoutPoll } from '@vueuse/core' const { data, isFinished, execute } = useFetch('/api/status', { immediate: false }) useTimeoutPoll(() => execute(), 10_000)

useTimeoutPoll会在execute()返回的 Promise 完成后才安排下一次轮询,避免对同一接口的并发请求。

仓库中的轮询生命周期实践参考

airi 仓库中的 packages/input-gamepad-vueuse/src/use-standard-gamepad.ts 展示了与Pausable完全一致的生命周期管理模式,可作为手写轮询组合式函数时的对照参考:

  • 通过isActive(shallowRef)维护轮询运行状态;
  • pause()内先判断isActive再停止,避免重复操作(第 79-85 行);
  • resume()内校验支持性与活跃状态后再启动(第 87-93 行);
  • 使用tryOnMounted(resume)在挂载时启动、tryOnScopeDispose在作用域销毁时清理(第 99-103 行)。

这些边界处理(幂等的 pause/resume、作用域自动清理)也正是useTimeoutPoll内建提供的——组合式函数随组件卸载自动释放定时器,无需手动clearTimeout。

使用建议与注意事项

  1. 后台节流:与所有基于setTimeout/setInterval的方案一样,页面处于后台或隐藏标签页时,浏览器可能降低定时器频率。若需要精确的固定节奏,应结合useDocumentVisibility在页面恢复可见时触发一次resume(配合immediateCallback)刷新状态。
  2. 错误处理:fn内部若抛错或返回 rejected Promise,轮询调度本身不受影响(下一次仍会按计划触发),但建议在回调内自行try/catch包裹网络请求,避免未捕获异常污染控制台。
  3. SSR 兼容性:VueUse 计时器系列对服务端渲染安全,定时器仅在客户端实际运行。
  4. 调用规则:在 airi 的技能体系中,useTimeoutPoll的调用规则为AUTO(见 SKILL.md),即开发 Vue 功能时,凡是"异步轮询"需求都应优先考虑它,而不是手写setInterval。
  • 人工智能
  • AI Agent
  • MCP 服务
  • 工具调用
  • GUI 自动化
  • 语音
  • 数字人
  • 交互助手

【免费下载链接】airi

💖🧸 自托管、归你拥有的 Grok 风格 AI 伴侣与 waifu / 赛博生命灵魂容器,目标是接近 Neuro-sama 的高度;支持实时语音聊天、Minecraft 和 Factorio 游玩,支持 Web / macOS / Windows。

项目地址:https://gitcode.com/moeru-ai/airi
点击查看免费下载

相关推荐

创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考

需要专业的网站建设服务?

联系我们获取免费的网站建设咨询和方案报价,让我们帮助您实现业务目标

立即咨询