watchEffect不直接等待异步,但可通过响应式状态(如isLoading、error)同步控制加载全流程:请求开始设true,finally统一设false,避免竞态或异常导致卡死,并结合模板条件渲染实现完整加载体验。
watchEffect 本身不直接“等待”异步完成,但它可以配合响应式状态,清晰、可控地管理 loading 状态的整个生命周期——从开始、进行中到结束(无论成功或失败)。关键不是让 watchEffect 去“监听 Promise”,而是让它驱动请求发起,并同步控制 loading 和 error 的响应式信号。
loading 状态必须是响应式的(如 ref(false)),并在请求逻辑的明确起点和终点赋值。注意:不能只在 await 前设 true、await 后设 false,否则可能因竞态或异常导致 loading 残留。
isLoading.value = true
finally 或对应分支里设 isLoading.value = false
isLoading.value = false 只写在 then 或 catch 里,漏掉 AbortError 或其他未捕获路径const isLoading = ref(false)const error = ref(null)const weatherData = ref(null)watchEffect(async (onInvalidate) => {if (!location.lat || !location.lng) returnisLoading.value = trueerror.value = nullconst controller = new AbortController()onInvalidate(() => controller.abort())try {const res = await fetch(`/api/weather?lat=${location.lat}&lng=${location.lng}`, {signal: controller.signal})if (!res.ok) throw new Error(`HTTP ${res.status}`)weatherData.value = await res.json()} catch (err) {if (err.name !== 'AbortError') {error.value = err.message || '获取天气失败'}} finally {isLoading.value = false // ✅ 所有路径统一收口}})
常见原因不是代码写错,而是:
isLoading.value = true 后立刻执行了长时间同步任务(比如大量计算、未用 await 的阻塞操作),导致 Vue 渲染队列没机会执行;isLoading.value = false 放在 await 后但没加 finally,一旦抛错就永远不重置;watchEffect 或 watch 同时修改同一 isLoading,互相覆盖。确保 loading 状态更新后,UI 有“喘息时间”:
watchEffect 内做繁重同步计算;await nextTick() 后再设 isLoading.value = false,但通常 finally 已足够。有了 isLoading 和 error,模板就能自然响应:
<template><div class="weather-card"><div v-if="isLoading" class="loading">正在获取天气…</div><div v-else-if="error" class="error">{{ error }}</div><div v-else-if="weatherData"><h3>{{ weatherData.location }}</h3><p>{{ weatherData.temp }}°C</p></div></div></template>
不复杂但容易忽略