
在 Vue 3 与 AI 桌面应用中优雅加载异步状态useAsyncState 完整实战指南【免费下载链接】airi Self hosted, you-owned Grok Companion, a container of souls of waifu, cyber livings to bring them into our worlds, wishing to achieve Neuro-samas altitude. Capable of realtime voice chat, Minecraft, Factorio playing. Web / macOS / Windows supported.项目地址: https://gitcode.com/GitHub_Trending/ai/airiuseAsyncState是 VueUse 提供的一个 State 分类组合式函数它把 Promise 驱动的异步流程封装为响应式状态组件挂载时无需阻塞setup函数Promise 一旦就绪界面会自动获得变更通知。本文以 useAsyncState.md 为骨架结合 airi 仓库中vueuse/core与自研use-async-state的落地代码完整讲解其返回值、选项、类型声明并给出适合搜索、检索与二次引用的工程化用法。一、核心机制为什么异步状态需要一个专用组合式函数在 Vue 3 中最常见的异步数据处理方式是直接在setup里写awaitconst data await fetchData()这种写法会阻塞 setup 的执行组件在数据就绪之前无法完成初始化。useAsyncState解决的核心问题正是这一点它把“一个 Promise 或异步函数”变成一个不会阻塞 setup、且会自动触发变更的响应式状态。默认情况下state是一个shallowRef也就是说只对引用本身做响应式追踪避免了深层代理带来的额外开销。在 airi 仓库中vueuse/core的useAsyncState被用于实际的产品代码例如ResizeHandler.vue 用它判断桌面端是否为 Windows 平台初始值为falsecontrols-island-hearing-config.vue 用它获取麦克风权限状态getMediaAccessStatus([microphone])初始值为not-determined并通过返回的execute实现手动刷新provider.ts 在 Pinia store 内部用它异步拉取 Provider 元数据同时配合computedAsync、useIntervalFn完成定时校验。这些场景有一个共同特征初始化是一个 Promise界面需要立即渲染一个“占位值”数据到达后再更新。这正是useAsyncState的典型适用面。二、基本用法把 Promise 变成响应式状态最基础的使用方式是把一个 Promise 传给useAsyncState同时提供一个初始状态import { useAsyncState } from vueuse/core import axios from axios const { state, isReady, isLoading, error } useAsyncState( axios .get(https://jsonplaceholder.typicode.com/todos/1) .then(t t.data), { id: null }, )要点说明第一个参数既可以是PromiseData也可以是(...args: Params) PromiseData这样的异步函数配合execute传参使用第二个参数initialState是MaybeRefData在第一次求值完成之前一直作为state的初值——这正是“先渲染占位、后刷新真实数据”的关键调用后组件会立即渲染初始状态Promise 解析完成后state自动更新isLoading与isReady同步翻转。返回值速查表属性说明state异步函数的执行结果isReady当 Promise 至少成功解析过一次时为trueisLoading当 Promise 处于 pending 状态时为trueerrorPromise 被拒绝时的错误对象execute重新执行异步函数支持可选延迟毫秒与透传参数executeImmediate立即重新执行等价于execute(0)在模板中可以这样使用template p v-ifisLoading加载中…/p p v-else-iferror出错了{{ error }}/p p v-else{{ state }}/p /template三、在 setup 中安全地等待结果thenable 返回值useAsyncState的返回值不仅是一个对象它还实现了PromiseLike接口见下文类型声明因此可以直接被await非常适合在async函数或script setup顶层使用const { state, isReady } await useAsyncState(fetchData, null) // 此时 state 已被填充isReady 为 true这种写法的价值在于当你确实需要在初始化阶段就拿到数据例如参与另一个初始化流程的编排时可以用await同步化而当你希望界面先行渲染时则省略await让数据“异步就绪”。同一个 API 同时满足两种需求。四、手动执行关闭 immediate 并控制触发时机默认情况下useAsyncState在创建时立即执行一次异步函数。通过immediate: false可以关闭自动执行改用execute/executeImmediate手动触发script setup langts import { useAsyncState } from vueuse/core const { state, execute, executeImmediate } useAsyncState(action, , { immediate: false }) async function action(event) { await new Promise(resolve setTimeout(resolve, 500)) return ${event.target.textContent} clicked! } /script template pState: {{ state }}/p button classbutton clickexecuteImmediate Execute now /button button classml-2 button clickevent execute(500, event) Execute with delay /button /template这里展示了一个非常实用的细节execute的第一个参数是延迟毫秒数其余参数会作为参数传给异步函数...args: Params因此点击事件对象event可以被直接透传进action。executeImmediate则是execute(0)的语法糖省去手动传0。在 airi 的麦克风权限场景中这一模式被用于“初始化不执行、用户点击后刷新”const { state: mediaAccessStatus, execute: refreshMediaAccessStatus } useAsyncState( () getMediaAccessStatus([microphone]), not-determined, )之后任意时刻调用refreshMediaAccessStatus()即可重新读取权限状态并驱动 UI 更新。五、选项Options逐一详解useAsyncState的第三个参数是UseAsyncStateOptions完整选项如下const { state } useAsyncState(promise, initialState, { // 创建后立即执行默认: true immediate: true, // 首次执行的延迟毫秒数默认: 0 delay: 0, // 每次执行前将 state 重置为初始值默认: true resetOnExecute: true, // 使用 shallowRef 保存 state默认: true shallow: true, // 出错时抛出异常而非吞掉错误默认: false throwError: false, // Promise 解析成功时的回调 onSuccess(data) { console.log(Success:, data) }, // Promise 被拒绝时的回调 onError(error) { console.error(Error:, error) }, })各选项的核心影响如下immediate是否在函数调用后立刻执行 Promise。设为false后必须手动调用execute/executeImmediate。注意设为true时delay依然生效即“延迟后立即执行”。delay首次执行的延迟毫秒数。结合immediate: true可以实现“挂载后稍等片刻再拉取数据”的节流式初始化。resetOnExecute每次执行前把state重置为initialState。当execute被多次调用例如做下拉刷新时该选项可以避免界面继续显示上一次的旧数据设为false则旧数据保留到新 Promise 解析完成。shallow是否用shallowRef保存状态。默认true意味着只有引用替换会触发更新适合大数据量、深层对象若希望状态被深层响应式代理例如要直接修改对象的某个字段并触发视图更新可设为false。throwError默认false时错误会被捕获并写入errorref不会中断调用链设为true后调用execute时错误会向上抛出方便在外层try/catch统一处理。onSuccess/onErrorPromise 成功/失败时的副作用回调适合做日志上报、Toast 提示等与状态无关的操作。六、类型声明从类型签名理解设计意图理解useAsyncState的完整类型声明有助于在 TS 项目中获得更好的类型推导export interface UseAsyncStateReturnBase Data, Params extends any[], Shallow extends boolean, { state: Shallow extends true ? RefData : RefUnwrapRefData isReady: Refboolean isLoading: Refboolean error: Refunknown execute: (delay?: number, ...args: Params) PromiseData | undefined executeImmediate: (...args: Params) PromiseData | undefined } export type UseAsyncStateReturn Data, Params extends any[], Shallow extends boolean, UseAsyncStateReturnBaseData, Params, Shallow PromiseLikeUseAsyncStateReturnBaseData, Params, Shallow export interface UseAsyncStateOptionsShallow extends boolean, D any { delay?: number // default 0 immediate?: boolean // default true onError?: (e: unknown) void onSuccess?: (data: D) void resetOnExecute?: boolean // default true shallow?: Shallow // default true throwError?: boolean // default false } export declare function useAsyncState Data, Params extends any[] any[], Shallow extends boolean true, ( promise: PromiseData | ((...args: Params) PromiseData), initialState: MaybeRefData, options?: UseAsyncStateOptionsShallow, Data, ): UseAsyncStateReturnData, Params, Shallow几个值得注意的类型细节Shallow extends boolean true是条件类型的关键当shallow为true时state是RefData为false时则是RefUnwrapRefData即深层解包后的引用。TS 会在编译期根据你的选项自动推导出正确类型。Params extends any[] any[]泛化了异步函数的参数列表配合execute(delay, ...args)实现“延迟 透传参数”的强类型签名。返回类型通过交叉类型 PromiseLike...同时具备“对象属性访问”与“await”两种能力这正是第三章中await useAsyncState(...)写法在类型层面的依据。七、仓库内的轻量自研版本对比与选型airi 仓库在 packages/stage-ui/src/composables/use-async-state.ts 中提供了一个仅约 40 行的自研轻量实现可以作为理解vueuse/core版内部原理的最佳教材import { ref } from vue export function useAsyncStateT( fn: () PromiseT, options?: { immediate?: boolean }, ) { const { immediate false } options ?? {} const state refT | undefined(undefined) const isLoading ref(false) const error refunknown(null) const execute async () { isLoading.value true error.value null try { state.value await fn() } catch (err) { error.value err } finally { isLoading.value false } } if (immediate) { execute() } return { state, isLoading, error, execute } }可以看到vueuse/core版本的核心逻辑本质上就是这三个 ref 加一个execute执行前将isLoading置true并清空errorawait成功后写入statecatch后写入errorfinally中复位isLoading。VueUse 完整版在此基础上补充了isReady、delay、resetOnExecute、shallow、throwError与回调等能力因此功能更全而自研版胜在零依赖、API 极简。自研版在仓库中还有一个典型的组合使用案例use-optimistic.ts 中useOptimisticMutation直接把“乐观更新”逻辑包装进useAsyncState的executereturn useAsyncState(async () { if (skipActionIf await skipActionIf()) { return undefined as R } const rollback await apply() // 先执行乐观更新拿到回滚函数 try { const result await action() // 再执行真实请求如 API 调用 if (onSuccess) { return await onSuccess(result) } return result as unknown as R } catch (err) { const allowRollback shouldRollback ? await shouldRollback(err as E) : true if (allowRollback typeof rollback function) { await rollback() // 出错时自动回滚 } if (onError) { await onError(err as E) } throw err } }, { immediate: !lazy })这一模式说明无论使用哪个版本useAsyncState的价值都在于把“执行状态机”loading / error / data统一收口让业务代码专注在真正的异步逻辑上。八、工程实践建议与常见误区综合官方文档与仓库内的落地代码给出以下实践建议初始化即需要占位值时务必提供合理的initialState例如权限状态初始为not-determined、平台判断初始为false避免首帧出现未定义导致渲染报错。需要手动刷新时关闭immediate并保留execute引用airi 的麦克风权限示例正是用execute: refreshMediaAccessStatus暴露给外部触发的。下拉刷新 / 重试时保持resetOnExecute: true默认值否则旧数据会残留在界面上用户无法感知“正在刷新”。大数据量或深层对象保持默认shallow: true只有需要直接修改状态内部字段并触发更新时才改为false。错误处理二选一需要全局兜底时保持throwError: false并监听errorref需要局部try/catch时开启throwError: true二者不要混用导致错误被吞掉或双重处理。不要用useAsyncState管理需要手动取消请求的场景例如竞态请求它没有内置取消机制此类需求应改用useAsyncQueue、computedAsync或额外的 AbortController 配合。仓库中与本文同目录的 computedAsync.md、useAsyncQueue.md 与 useAsyncValidator.md 提供了异步领域的相邻方案可作为选型时的横向参考。小结useAsyncState用一个组合式函数优雅地统一了异步数据的 loading、error、data 三种状态且天然兼容script setup的await语法。无论是直接用vueuse/core的完整版还是参考 packages/stage-ui/src/composables/use-async-state.ts 的自研轻量版掌握其返回契约与选项语义都能让 Vue 3 项目中的异步 UI 更简洁、更健壮。【免费下载链接】airi Self hosted, you-owned Grok Companion, a container of souls of waifu, cyber livings to bring them into our worlds, wishing to achieve Neuro-samas altitude. Capable of realtime voice chat, Minecraft, Factorio playing. Web / macOS / Windows supported.项目地址: https://gitcode.com/GitHub_Trending/ai/airi创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考