Skip to content
← Todas las preguntas
🔥 Avanzado

¿Por qué los watchers creados dentro de callbacks asíncronos causan pérdidas de memoria?

⚛️ Reactividad❌ Errores comunes👁️ Watchers

Porque Vue solo limpia automáticamente los watchers creados de forma síncrona durante setup(). Cuando creas un watch o watchEffect dentro de un setTimeout, Promise.then o después de un await, Vue no puede vincularlo al ciclo de vida del componente. Sigue ejecutándose después de que el componente se desmonte.

ts
onMounted(async () => {
  await loadInitialData()

  // Este watcher NO está vinculado al componente
  watch(data, (newVal) => {
    processData(newVal) // sigue ejecutándose tras el desmontaje
  })
})

El mismo problema con setTimeout:

ts
onMounted(() => {
  setTimeout(() => {
    watchEffect(() => {
      console.log(data.value) // sigue ejecutándose tras el desmontaje
    })
  }, 1000)
})

Cómo solucionarlo

Opción 1 (preferida): Crea el watcher de forma síncrona con lógica condicional dentro.

ts
const config = ref(null)
const userData = ref(null)

// Creado de forma síncrona, limpiado automáticamente al desmontar
watch(userData, (newData) => {
  if (config.value && newData) {
    applySettings(config.value, newData)
  }
})

onMounted(async () => {
  config.value = await fetchConfig()
})

Opción 2: Guarda la función de parada y llámala manualmente al desmontar.

ts
let stopWatcher: (() => void) | null = null

onMounted(async () => {
  await loadData()

  stopWatcher = watch(data, (newVal) => {
    processData(newVal)
  })
})

onUnmounted(() => {
  stopWatcher?.()
})

La primera opción es casi siempre mejor. Si puedes reestructurar la lógica para que el watcher se cree de forma síncrona y la condición asíncrona se compruebe dentro del callback, evitas por completo la limpieza manual.

Ver también: ¿Por qué mi watchEffect pierde dependencias después de un await? · ¿Qué es effectScope y cuándo lo usarías?

Referencias

Publicado bajo la licencia MIT.