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.
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 elconst 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 vezconst 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 juntosconst 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 };});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 componenteexport const route = { preload: ({ params }) => getUsuario(Number(params.id)),};// en el componente: createAsync reusa el query ya en vuelo, sin cascadafunction 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.
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.