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 árbol de ownership tiene un talón de Aquiles: solo existe durante la ejecución síncrona. En el instante en que tu código cede el control —un await, un then, un setTimeout, el callback de un evento diferido—, la variable del owner actual vuelve a estar vacía, y todo lo reactivo que crees a partir de ahí nace huérfano. Solid resuelve esto con dos primitivas quirúrgicas: getOwner, que fotografía el dueño en el momento correcto, y runWithOwner, que te devuelve a su contexto cuando lo necesites. Son la herramienta imprescindible para que lo asíncrono no rompa la disposición automática.
- Entender por qué el owner actual solo vive durante la ejecución síncrona.
- Usar
getOwnerpara capturar el dueño antes de un salto asíncrono. - Usar
runWithOwnerpara re-entrar en ese contexto y enganchar efectos y limpiezas. - Reconocer los escenarios donde hace falta: promesas, temporizadores y callbacks externos.
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.
flowchart LR S[cuerpo sincrono owner activo] --> G[getOwner captura el dueno] S --> A[await rompe el contexto] A --> N[owner actual es null] N --> R[runWithOwner reentra] G -.pasa la referencia.-> R R --> E[efecto y cleanup enganchados al dueno] style S fill:#89b4fa,color:#11111b style N fill:#f38ba8,color:#11111b style R fill:#a6e3a1,color:#11111b style E fill:#a6e3a1,color:#11111b
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 desmontar
tarea().then((r) => {
if (!vivo) return; // no re-entres en un owner muerto
runWithOwner(owner, () => aplicar(r));
});
runWithOwner no comprueba si el owner ya fue dispuesto: ejecutará fn de todas formas. Si el componente se desmontó durante el await, estarás creando efectos dentro de un ámbito difunto, que no volverá a disponerse y puede volver a fugar. Cuando el salto asíncrono pueda ser largo, guarda un flag de “vivo” cerrado por un onCleanup síncrono, o comprueba una condición de montaje antes de re-entrar. La regla sana: re-entra solo si sigues necesitando el resultado.
Buena parte del trabajo asíncrono en Solid no necesita runWithOwner a mano, porque createResource, Suspense y las utilidades de datos ya capturan y restauran el owner internamente. getOwner y runWithOwner son las herramientas de bajo nivel para cuando sales de esos caminos: integraciones con librerías imperativas, APIs del navegador basadas en callbacks, o flujos asíncronos hechos a mano.
La lección profunda de este par de primitivas es que en Solid “estar dentro de un componente” no es una propiedad ambiental que flota en el aire mientras el componente existe, sino un valor concreto y capturable: el owner. En React, hooks como useEffect funcionan por su posición en el árbol de llamadas del render, y por eso no puedes invocarlos tras un await —no hay forma de recuperar la posición—. Solid materializa ese contexto en un objeto que puedes guardar en una variable, pasar por parámetro y volver a activar cuando te convenga. Eso convierte lo asíncrono, que en otros modelos es una zona prohibida para la creación de estado, en territorio perfectamente habitable: el dueño no se pierde porque el tiempo pase, solo deja de estar activo, y reactivarlo es un runWithOwner. Esta cosificación del ámbito —volver dato lo que en otros frameworks es magia posicional— es la misma idea que hace que un signal sea una función que puedes pasar, o que un efecto sea un nodo del árbol que puedes inspeccionar. Cuando entiendes que el owner es un valor de primera clase, dejas de temer el await y empiezas a tratar la disposición como algo que tú controlas, no que se te escapa.
- Reproduce la fuga: crea un efecto dentro de un
thensinrunWithOwnery confirma con el aviso de consola que nace huérfano. - Captura el owner con
getOwnerantes de la promesa y envuelve la creación del efecto enrunWithOwner; verifica que ahora se dispone al desmontar. - Haz lo mismo desde un
setTimeouten lugar de una promesa y comprueba que el mecanismo es idéntico. - Añade una guarda que evite re-entrar si el componente ya se desmontó, usando un flag cerrado en un
onCleanupsíncrono. - Explica en una frase por qué
getOwnerdebe llamarse antes delawaity nunca después.