Cuando cada petición necesita la respuesta de la anterior, los tiempos se suman. Se ataca en tres frentes: pedir a la vez, pedir en bloque y no volver a pedir.
Una petición HTTP es un viaje: el navegador pregunta y el servidor contesta. Aunque la respuesta sea diminuta, cuesta una ida y vuelta (RTT: lo que tarda un mensaje en ir y volver), del orden de 50-200 ms en red móvil. No depende del tamaño del cuerpo, sino de la distancia y de las vueltas.
Una cascada (waterfall) es una secuencia en la que cada petición espera a que termine la anterior. En Network se ve como una escalera: barras que empiezan donde termina la de arriba. Esa escalera es la firma visual de una dependencia.
Piénsalo como mandar a un mensajero. Le pides un tornillo; vuelve, y descubres que faltaba el taquete; lo mandas otra vez; luego, la broca. Cuatro recados encadenados son cuatro viajes. Con cuatro mensajeros a la vez, es uno. Y con la lista completa en una sola nota, también.
La pantalla pide el perfil y, con su id, pedidos, saldo y avisos. Encadenado son cuatro vueltas de 180 ms: unos 720 ms antes de pintar algo. Si las tres últimas solo necesitan un id que ya tienes, pueden salir juntas: 180 ms más pintar.
Un endpoint que agrupa gana incluso a cuatro peticiones perfectas, porque lo que se paga no es el byte: es la vuelta. Cuatro viajes son cuatro colas, cuatro cabeceras y cuatro claves que invalidar; uno solo es una vuelta. Por eso la misma pantalla se siente ocho veces más lenta sin que ninguna petición lo sea: la percepción no suma milisegundos, suma esperas.
Paralelo. Si dos peticiones no dependen una de otra, no hay razón para esperar: Promise.all las lanza juntas. El navegador abre varias conexiones por dominio, así que el reloj corre una vez. Sirve para lo independiente: no se puede paralelizar lo que necesita un dato que no llegó.
Bloque. Un endpoint que devuelve la pantalla completa convierte cuatro viajes en uno: cuesta un join en el backend y suele ser el arreglo de fondo.
Caché HTTP. El navegador guarda la respuesta si el servidor lo permite: Cache-Control: max-age=60 significa "vale un minuto, no preguntes". Pasado el minuto revalida con ETag y el servidor contesta "no cambió" (304): otra vuelta, pero sin cuerpo.
Caché de aplicación. Aquí guardas tú los datos. El patrón stale-while-revalidate sirve la copia al instante y pide la nueva en segundo plano: el usuario ve algo ya y la pantalla se corrige sola. Es la nota con el teléfono en el corcho: respondes sin mensajero, pero si el número cambió, la nota miente.
El problema difícil es la invalidación: una caché es una promesa sobre cuándo cambian los datos, y eso casi nadie lo sabe. Un TTL es una apuesta; invalidar por evento es exacto pero necesita un canal que avise. Si guardas y no invalidas, el cambio del usuario desaparece al recargar.
El patrón N+1 es la cascada con disfraz: una petición para la lista y una por elemento. Con 30 filas son 31 vueltas, y crecen con los datos. Se detecta contando peticiones por pantalla, no por su lentitud. Se arregla en el servidor con un join o un lote por ids; en el cliente, pidiendo el detalle al abrir la fila.
// En serie: cada await espera a la anterior. Los tiempos se suman. const get = url => fetch(url).then(r => r.json()); const perfil = await get('/api/perfil'); const pedidos = await get(`/api/pedidos?u=${perfil.id}`); const saldo = await get(`/api/saldo?u=${perfil.id}`); const avisos = await get(`/api/avisos?u=${perfil.id}`); // ~4 × 180 ms ≈ 720 ms antes de pintar nada // En paralelo: solo si las tres son independientes del perfil. const [p, s, a] = await Promise.all([ get('/api/perfil'), get('/api/saldo'), get('/api/avisos'), ]); // ~180 ms, pero siguen siendo tres viajes y tres fallos // Agrupado: un viaje y un contrato. Lo pide el frontend, // lo sirve el backend. const resumen = await get('/api/resumen?u=42'); // ~180 ms: una petición que cachear, una clave que invalidar
Con tres datos, no con una opinión: cuántas peticiones hace la pantalla, cuánto es ida y vuelta y qué forma tendría el endpoint que lo sustituye. "Hace 31 peticiones, 30 son una por fila" abre la conversación; "va lento" no.
En Network, la forma importa más que la lista de tiempos.
| Herramienta | Qué se mira | Cómo se ve el síntoma |
|---|---|---|
| Network › cascada | Si se solapan o van en escalón. | Cuatro barras en escalera: ninguna empieza antes de que acabe la anterior. |
| Network › Queueing y Stalled | El tiempo gris antes de enviar, separado del de respuesta. | Queueing alto: espera a que se libere una conexión. Stalled: prioridad o socket ocupado. |
| Network › TTFB | El tiempo hasta el primer byte. | TTFB alto y descarga diminuta: el problema es la vuelta, no el tamaño. |
| Network › n.º de peticiones | Peticiones al cargar la pantalla. | 31 donde deberían ser 2 o 3: la forma del N+1. |
“Serial requests add up: each one waits for the previous response, so latency multiplies by the number of steps.”
“I parallelize independent calls with Promise.all, but the real win is one endpoint that returns the whole screen.”
“I cache with stale-while-revalidate and treat invalidation as the hard part: a cache key is a promise about when data changes.”
Comprueba que lo tienes
Cuatro preguntas. Al comprobar se marca la lección como completada.
Cuatro peticiones de 180 ms encadenadas tardan ~720 ms. ¿Por qué el total no es 180 ms?
Por qué: la suma es hija de la dependencia, no de la red. El navegador abre varias conexiones, pero no puede inventar el id que la siguiente necesita. Si fueran independientes, saldrían juntas y el reloj correría una vez.
Promise.all sobre cuatro peticiones independientes tarda ~180 ms. ¿Qué NO resuelve?
Por qué: el paralelo esconde la espera entre peticiones, no reduce las vueltas. Cada una conserva su cola, su primer byte y su timeout; agrupar cambia la forma. Y del mismo dominio salen varias conexiones.
El usuario guarda un cambio y ve datos viejos durante un segundo. Con stale-while-revalidate, ¿qué está pasando?
Por qué: stale-while-revalidate cambia frescura por velocidad: muestra lo que tiene y se corrige sola. Ver lo viejo un instante es el trato; el error es no invalidar tras escribir, porque la copia vieja vuelve y el usuario cree que no guardó.
En Network ves una petición de lista y 30 pequeñas, una por fila. ¿Qué es y qué se pide?
Por qué: el N+1 es un problema de forma: 1 + N vueltas crecen con los datos y ninguna técnica de cliente borra peticiones. O el servidor hace el join, o se pide un lote de ids. El debounce cambia cuándo se disparan, no cuántas son.