getOwner y runWithOwner
El owner solo está activo durante la ejecución síncrona; tras un await se pierde. getOwner captura el dueño en el momento correcto y runWithOwner vuelve a entrar en su contexto para que efectos, limpiezas y contexto se enganchen donde deben.
El owner vive solo en lo síncrono
Solid fija la variable del owner actual justo antes de correr el cuerpo de una computación y la restaura al terminar. Ese “terminar” es el final de la ejecución síncrona. Un await parte la función en dos: lo que hay antes corre con el owner puesto; lo que hay después corre más tarde, en otro turno del bucle de eventos, cuando el owner ya volvió a ser null.
import { createEffect, onCleanup } from "solid-js";function Grafica(props) { cargar(props.id).then((serie) => { // AQUI el owner ya es null: estamos en otro turno del event loop createEffect(() => dibujar(serie, tema())); // HUERFANO: no se dispone onCleanup(() => destruir()); // NO se agenda: sin owner }); return <canvas />;}El efecto creado dentro del then no pertenece al owner de Grafica: cuando el componente se desmonte, nadie lo dispondrá. Seguirá vivo, reaccionando a tema, dibujando sobre un canvas que ya no existe. Es una fuga silenciosa, y el onCleanup de al lado ni siquiera llega a agendarse.
getOwner: fotografiar el dueño
getOwner() devuelve el owner activo en el instante en que lo llamas —o null si no hay ninguno—. La clave es cuándo lo llamas: tienes que hacerlo mientras el owner sigue puesto, es decir, de forma síncrona en el cuerpo del componente o del efecto, antes del salto asíncrono. Guardas esa referencia en una constante y la conservas para más tarde.
import { getOwner } from "solid-js";function Grafica(props) { const owner = getOwner(); // capturado AHORA, con el owner aun activo cargar(props.id).then((serie) => { // owner sigue apuntando al dueno de Grafica, aunque ya no este activo }); return <canvas />;}getOwner no cambia nada del sistema: solo lee. Su valor es una referencia al nodo del árbol que veíamos en la primera lección, con sus hijos, sus limpiezas y su contexto. Tenerla en la mano es tener la llave para volver.
runWithOwner: re-entrar en el contexto
runWithOwner(owner, fn) ejecuta fn como si el owner activo fuese el que le pasas. Durante esa ejecución, cualquier createEffect, createMemo u onCleanup se engancha a ese owner; el contexto de createContext vuelve a estar disponible y los límites de error del subárbol se restauran. Devuelve lo que devuelva fn. Combinada con getOwner, cierra el agujero asíncrono:
import { getOwner, runWithOwner, createEffect, onCleanup } from "solid-js";function Grafica(props) { const owner = getOwner(); cargar(props.id).then((serie) => { runWithOwner(owner, () => { createEffect(() => dibujar(serie, tema())); // ahora es hijo de Grafica onCleanup(() => destruir()); // ahora SI se agenda }); }); return <canvas />;}Ahora el efecto y su limpieza pertenecen al owner de Grafica: cuando el componente se desmonte, Solid dispondrá el subárbol y destruir correrá. La disposición automática vuelve a funcionar pese al await de por medio.
El patrón no se limita a las promesas. Sirve siempre que quieras crear reactividad desde un callback que Solid no controla: el registro de eventos de una librería de terceros, un setTimeout, el resolvedor de un evento personalizado. Capturas el owner arriba y envuelves en runWithOwner el trozo que crea efectos o limpiezas.
Un caso real: puentear un emisor externo
Imagina una librería de sincronización que te avisa por un callback cuando llegan datos, y quieres crear reactividad nueva cada vez que eso ocurre. Ese callback lo invoca la librería, fuera de todo owner; sin re-entrar, cualquier efecto que crees dentro será huérfano.
import { getOwner, runWithOwner, createSignal, createEffect } from "solid-js";function Sincronizado(props) { const owner = getOwner(); const cliente = conectar(props.sala); cliente.alRecibir((datos) => { runWithOwner(owner, () => { const [estado, setEstado] = createSignal(datos); createEffect(() => pintar(estado())); // hijo de Sincronizado }); }); onCleanup(() => cliente.desconectar()); // sincrono: no necesita re-entrar return <section />;}Fíjate en el reparto de responsabilidades: la desconexión del cliente se registra de forma síncrona en el cuerpo —tiene owner, no necesita nada especial—, mientras que lo que nace dentro del callback asíncrono se envuelve en runWithOwner. Cada pieza se engancha al mismo owner de Sincronizado, y todo se dispone junto al desmontar.
Cuando el salto puede tardar, protege la re-entrada con una condición de vida, para no crear efectos en un ámbito ya difunto:
import { getOwner, runWithOwner, onCleanup } from "solid-js";const owner = getOwner();let vivo = true;onCleanup(() => (vivo = false)); // se apaga al desmontartarea().then((r) => { if (!vivo) return; // no re-entres en un owner muerto runWithOwner(owner, () => aplicar(r));});