Componer derivaciones async: encadenar sin cascadas manuales
Cómo se encadenan varias createAsync que dependen unas de otras leyendo el accessor de la anterior, dejando que el grafo secuencie lo que es realmente dependiente sin un solo booleano de carga orquestado a mano. La distinción crítica entre una cascada legítima por dependencia de datos y una cascada accidental que debería ser paralelo, cómo paralelizar con derivaciones independientes o Promise.all, y cómo preload saca la petición del render para matar la cascada de renderizar-luego-pedir.
El dolor clásico de encadenar peticiones es la orquestación manual: pides A, guardas un booleano de carga, cuando A llega pides B con su dato, guardas otro booleano, y así montas una máquina de estados frágil que se rompe en cuanto una fuente cambia a destiempo. createAsync disuelve ese ritual. Como cada derivación se rastrea sola y suspende cuando su dato no está, encadenar dos es tan simple como que la segunda lea el accessor de la primera. El grafo se encarga de secuenciar. Tu trabajo deja de ser coordinar el tiempo y pasa a ser algo mucho más fino: distinguir qué es dependiente de verdad y qué solo lo parece.
- Encadenar derivaciones dependientes leyendo el accessor de una dentro de otra.
- Distinguir una cascada legítima por dependencia de datos de una accidental.
- Paralelizar lo independiente con derivaciones separadas o con
Promise.all. - Usar
preloadpara sacar la petición del render y eliminar la cascada de arranque.
Encadenar por dependencia real
Cuando el dato B necesita el dato A —los posts de un usuario necesitan primero al usuario— la dependencia es real y la secuencia inevitable. Con createAsync la expresas sin ceremonia: la segunda derivación lee la primera. Si el usuario aún no está, leer usuario() suspende, y la petición de posts sencillamente aún no arranca; cuando el usuario resuelve, la derivación de posts se ejecuta con su valor.
import { createAsync } from "@solidjs/router";
const usuario = createAsync(() => getUsuario(props.id));
const posts = createAsync(() => {
const u = usuario(); // suspende hasta que el usuario resuelve
return getPostsDe(u.id); // solo corre cuando ya hay usuario
});
No hay un solo if (cargandoUsuario) return, ningún booleano intermedio, ninguna comprobación de nulos. La secuencia —primero el usuario, después sus posts— emerge de que la segunda derivación depende de la primera, y el grafo la respeta. Has descrito qué depende de qué; el sistema deduce cuándo corre cada cosa.
Cascada legítima frente a cascada accidental
Aquí está la sutileza que separa el código rápido del lento. Una cascada es legítima cuando B de verdad necesita a A: no hay forma de pedir los posts sin conocer antes el usuario. Pero muchas cascadas son accidentales: dos datos que no dependen entre sí se piden en secuencia solo porque el código los encadenó por inercia. Encadenar lo independiente convierte dos peticiones que podían volar juntas en una espera sumada.
// ACCIDENTAL: notificaciones no necesita el usuario, pero espera por el
const usuario = createAsync(() => getUsuario(props.id));
const notis = createAsync(() => {
usuario(); // dependencia FALSA: no usa su valor
return getNotificaciones(props.id);
});
// PARALELO: dos derivaciones independientes vuelan a la vez
const usuario2 = createAsync(() => getUsuario(props.id));
const notis2 = createAsync(() => getNotificaciones(props.id));
La regla es nítida: encadena solo cuando el valor de A entra en la petición de B. Si B no usa el dato de A, no lo leas; dale su propia derivación y déjalo volar en paralelo. Cuando varios datos independientes deben resolverse juntos dentro de una sola derivación, Promise.all los lanza a la vez y espera por el conjunto.
// paralelo dentro de una derivacion: ambos arrancan juntos
const panel = createAsync(async () => {
const id = props.id; // dependencia rastreada antes del await
const [u, n] = await Promise.all([
getUsuario(id),
getNotificaciones(id),
]);
return { usuario: u, notis: n };
});
flowchart LR subgraph Cascada legitima U1[getUsuario] --> P1[getPosts necesita el usuario] end subgraph Cascada accidental mala U2[getUsuario] --> N2[getNotificaciones espera sin motivo] end subgraph Paralelo correcto U3[getUsuario] N3[getNotificaciones] end style P1 fill:#a6e3a1,color:#11111b style N2 fill:#f38ba8,color:#11111b style U3 fill:#89b4fa,color:#11111b style N3 fill:#89b4fa,color:#11111b
Preload: sacar la petición del render
Queda una cascada más traicionera que ninguna dependencia de datos explica: la de renderizar-luego-pedir. Si la petición nace dentro del componente, no puede arrancar hasta que el componente se monta; y si el componente hijo pide algo, no arranca hasta que el padre lo renderiza. El árbol se convierte en una escalera de esperas donde cada nivel bloquea al siguiente, aunque los datos no dependan entre sí.
Solid Router lo corta de raíz con preload. Declaras en la ruta una función que dispara los query en cuanto hay intención de navegar —al pasar el ratón, al empezar la transición— mucho antes de que el componente se monte. Cuando el componente por fin lee su createAsync, el query ya está en vuelo o resuelto, y la cascada de arranque desaparece.
import { query, createAsync } from "@solidjs/router";
const getUsuario = query((id: number) => fetch(`/api/u/${id}`).then((r) => r.json()), "usuario");
// en la definicion de la ruta: dispara el query antes de montar el componente
export const route = {
preload: ({ params }) => getUsuario(Number(params.id)),
};
// en el componente: createAsync reusa el query ya en vuelo, sin cascada
function Perfil(props: { params: { id: string } }) {
const usuario = createAsync(() => getUsuario(Number(props.params.id)));
return <Suspense fallback={<Spinner />}><h1>{usuario().nombre}</h1></Suspense>;
}
Como query deduplica por clave, la llamada del preload y la del createAsync son la misma petición: preparas los datos temprano y los lees tarde, sin pedirlos dos veces. Esta es la pieza que hace que las apps de Solid Router carguen sin la escalera de esperas típica del render bloqueante.
La cascada accidental casi nunca se nota, porque funciona: los datos llegan, solo que más tarde de lo necesario. Revisa cada derivación encadenada con una pregunta única —¿la petición de abajo usa el valor de la de arriba?—. Si la respuesta es no, has cosido una espera gratuita. La cura es separar en derivaciones independientes para que vuelen en paralelo, o agruparlas en un Promise.all cuando deban resolverse juntas. La dependencia entre datos debe leerse en el código exactamente cuando existe en la realidad, ni una vez más.
Cuando varias derivaciones independientes cargan en paralelo, a veces no quieres que aparezcan en desorden según cuál resuelva antes. SuspenseList coordina varias fronteras Suspense hermanas y gobierna el orden en que se revelan, para que el paralelismo en la red no se traduzca en un baile visual de bloques que saltan a la vista sin concierto.
import { SuspenseList } from "solid-js";
// carga en paralelo por debajo, revela de arriba abajo en orden
<SuspenseList revealOrder="forwards">
<Suspense fallback={<Skeleton />}><Perfil /></Suspense>
<Suspense fallback={<Skeleton />}><Notificaciones /></Suspense>
</SuspenseList>
Paralelizas la obtención por debajo y ordenas la aparición por arriba: dos decisiones independientes que SuspenseList te deja tomar por separado, sin sacrificar la una por la otra.
Encadena lo dependiente
B lee el accessor de A solo cuando usa su valor. El grafo secuencia sin booleanos de carga orquestados a mano.
Paraleliza lo independiente
Derivaciones separadas vuelan a la vez. Promise.all agrupa lo que debe resolverse junto dentro de una sola.
Precarga con preload
Dispara los query antes de montar el componente. query deduplica, asi que preparar y leer son la misma peticion.
Casi todo el mundo trata las cascadas como un asunto de rendimiento de red y busca la cura en cachés, batching o protocolos más rápidos. Pero la mayoría de las cascadas no nacen en la red: nacen en el código, en el instante en que un programador escribe una dependencia que la realidad no impone. createAsync clarifica esto de una forma que ninguna herramienta imperativa podía, porque hace que la dependencia entre datos sea literalmente la lectura de un accessor dentro de otro. Y entonces la regla se vuelve visible y exacta: hay una dependencia cuando, y solo cuando, el valor de una petición entra en otra. Si escribes esa lectura, has declarado una secuencia y el grafo la honrará suspendiendo la segunda hasta que la primera resuelva —una cascada legítima, la única clase que merece existir—. Si no la escribes, las dos derivaciones son independientes y el sistema las deja volar juntas. Todo el arte de componer datos asíncronos en Solid se reduce a esta higiene: escribir la dependencia exactamente donde existe en el dominio y en ningún otro sitio. Las cascadas accidentales son dependencias fantasma, lecturas que el código contrajo por inercia y que la realidad nunca pidió; las matas borrando la lectura, no optimizando la red. Y la cascada más venenosa de todas, la de renderizar-luego-pedir, ni siquiera es una dependencia entre datos: es una dependencia entre el dato y el ciclo de vida del componente, y por eso no se cura reordenando derivaciones sino sacando la petición del render con preload, para que los datos empiecen a viajar antes de que exista el componente que los mostrará. Quien ve las cascadas como lo que son —afirmaciones sobre qué necesita a qué— deja de pelear con la red y empieza a escribir dependencias con la precisión de quien sabe que cada lectura de más es una espera de más.
- Encadena
usuarioy suspostscon doscreateAsync, leyendo el primero dentro del segundo, y confirma que los posts no arrancan hasta que el usuario resuelve. - Introduce una cascada accidental leyendo
usuario()en una derivación de notificaciones que no usa su valor; mide el retraso y arréglalo separándolas. - Agrupa dos peticiones independientes en un
Promise.alldentro de una sola derivación y verifica que arrancan a la vez. - Declara un
preloaden la ruta que dispare elquerydel usuario y comprueba, con las herramientas de red, que la petición empieza antes de montar el componente. - Explica por qué
querypermite quepreloadycreateAsynccompartan una única petición, y qué pasaría si el fetcher no estuviera deduplicado.