Breakpoints de red: cazar la petición fantasma
Detenerse en el código que origina una petición, la sintaxis de coincidencia por URL, y el flujo completo desde el panel de Network hasta la línea responsable.
Ves una petición en el panel de red y no sabes quién la hace. O peor: no la ves y debería estar ahí. Las dos preguntas se responden con la misma herramienta, un punto de parada que se dispara cuando una petición cuya URL contiene cierto fragmento está a punto de salir, deteniendo la ejecución en el código que la originó. Es la familia más simple de las cuatro y la que cierra el hueco entre el panel de red y el de Sources, que hasta ahora eran dos mundos separados.
- Poner puntos de parada por fragmento de URL y usar el comodín de cualquier petición.
- Ir del panel de red a la línea de código que originó una petición.
- Diagnosticar peticiones duplicadas, peticiones que no salen y peticiones con la URL mal construida.
- Combinar el breakpoint de red con la columna de iniciador cuando cada uno es mejor.
Cómo se ponen
En el subpanel de breakpoints de XHR y fetch, el botón de añadir abre un campo donde escribir un fragmento de URL. La coincidencia es por subcadena simple: no hay expresiones regulares ni comodines. Escribir /api/pedidos casa con cualquier URL que contenga esa secuencia en cualquier posición.
Dejando el campo vacío se crea el punto de parada de cualquier petición, que se dispara con todas. Es ruidoso y es exactamente lo que quieres cuando no sabes ni qué URL buscar.
El punto se dispara antes de que la petición salga, con la ejecución detenida en el código que la inició. Eso significa dos cosas útiles: la pila apunta al origen, y puedes inspeccionar y hasta modificar los argumentos antes de continuar.
Pese al nombre, el punto de parada cubre tanto XMLHttpRequest como fetch. No cubre las peticiones que no origina JavaScript: la carga de imágenes por un atributo src, las hojas de estilo, las fuentes, ni las peticiones de navegación. Para esas, la vía es el iniciador del panel de red.
Los cuatro casos
Caso uno: la petición duplicada. El panel de red muestra la misma llamada dos o tres veces. Pon el punto de parada con el fragmento de la URL y reproduce. Te detendrás una vez por petición, y comparando las pilas sabrás si es el mismo código ejecutándose varias veces —efecto sin dependencias correctas, listener duplicado, componente montado dos veces— o dos módulos distintos pidiendo lo mismo sin saber el uno del otro. Son dos bugs con arreglos completamente distintos y la pila los separa en diez segundos.
Caso dos: la petición que no sale. Esperas una llamada y en el panel no aparece. Pon el punto de parada con la URL. Si no se dispara, la llamada no se está haciendo, y el problema está antes: una condición que no se cumple, un manejador que no se ejecuta, una promesa que nunca resuelve. Eso descarta de golpe toda la mitad de red del problema. Si sí se dispara y aun así no aparece en el panel, tienes otro caso: puede que la esté sirviendo un service worker desde caché, que un filtro del panel la esté ocultando, o que se cancele antes de salir.
Caso tres: la URL mal construida. Ves una petición a algo con un undefined en medio, o con un identificador que no toca. El punto de parada con el fragmento sospechoso te detiene justo antes, con todo el estado disponible: puedes inspeccionar las variables de las que se construyó la URL y ver cuál tenía el valor malo. Es muchísimo más directo que reconstruir hacia atrás desde la cadena final.
Caso cuatro: la petición que no debería existir. Una llamada a un endpoint que nadie recuerda haber programado. Aquí la respuesta es la pila, y a menudo revela que la hace una librería de terceros, un script de analítica, o una extensión.
Del panel de red a la línea de código
El flujo completo que une los dos paneles, y que conviene tener memorizado.
Localiza la petición en el panel de red. La columna de iniciador muestra qué provocó la petición, con un enlace directo al fichero y la línea. Pasar el cursor sobre esa celda despliega la cadena de iniciadores completa: quién cargó al que cargó al que hizo la petición.
Si el iniciador apunta a código de librería —que es lo habitual con cualquier cliente HTTP— el enlace te deja dentro del cliente, no en tu código. Ahí es donde el iniciador se queda corto y el breakpoint de red gana: con el punto de parada puesto, la pila completa incluye tus marcos y puedes navegar hasta el tuyo.
Hay además un dato en el panel de red que la gente no mira y que ahorra este paso entero: manteniendo la tecla de mayúsculas mientras pasas el cursor sobre una petición, el panel colorea en verde su iniciador y en rojo las peticiones que ella misma inició. En una cascada de sesenta peticiones eso reconstruye el grafo de dependencias visualmente.
Interceptar sin detener
Cuando quieres la información pero no la parada, la alternativa es envolver el método, con la ventaja de que puedes filtrar y acumular.
// Registra todas las peticiones fetch con su pila, sin detener nada
const original = window.fetch;
window.fetch = function (recurso, opciones = {}) {
const url = String(recurso instanceof Request ? recurso.url : recurso);
const inicio = performance.now();
console.groupCollapsed(`%c${(opciones.method || 'GET').toUpperCase()}%c ${url}`,
'background:#89b4fa;color:#11111b;padding:1px 5px;border-radius:3px', '');
console.trace();
console.groupEnd();
return original.apply(this, arguments).then(r => {
console.debug(`${r.status} ${url} en ${Math.round(performance.now() - inicio)} ms`);
return r;
});
};
Esa envoltura da lo mismo que el breakpoint pero de forma pasiva, y añade el tiempo y el código de estado. Para investigar una secuencia completa de peticiones es más práctica que detenerse veinte veces. Para deshacerla, recarga.
Y hay una variante que resuelve el caso de la petición ocasional entre muchas.
// Detener solo en la peticion que cumple una condicion
const original = window.fetch;
window.fetch = function (recurso, opciones) {
const url = String(recurso instanceof Request ? recurso.url : recurso);
if (url.includes('/api/pedidos') && (opciones?.method === 'DELETE')) debugger;
return original.apply(this, arguments);
};
Eso es un breakpoint de red condicional, que la interfaz no ofrece: solo se detiene en las peticiones de borrado a ese endpoint, ignorando las decenas de lecturas.
Hay un caso que rompe todos los modelos mentales de esta lección y que conviene conocer porque produce una de las confusiones más largas que existen: la petición que tu código hace, que el panel de red muestra, y que nunca salió de la máquina. Ocurre cuando hay un service worker registrado. Un service worker intercepta cada petición del ámbito que controla y puede responderla desde su propia caché sin tocar la red, y lo hace de forma completamente transparente para tu código: el fetch resuelve con una respuesta perfectamente normal. En el panel de red esas peticiones aparecen marcadas —hay un indicador que señala que la respuesta vino del service worker, y la columna de tamaño muestra un valor especial en lugar de bytes transferidos— pero es una marca discreta y muy fácil de pasar por alto cuando estás mirando otra cosa. Las consecuencias en depuración son tres y todas producen horas perdidas. Primera: has desplegado y no ves los cambios, porque el service worker sirve la versión vieja desde su caché y ninguna recarga normal lo evita; ni siquiera la recarga forzada, porque esa afecta a la caché HTTP y no a la del service worker. Segunda: cambias algo en el servidor y la respuesta sigue siendo la anterior, y empiezas a dudar del despliegue, del CDN y de tu propia cordura. Tercera, la más sutil: el throttling de red no afecta a lo que sirve el service worker, así que tus pruebas con red lenta dan resultados irreales para todo lo cacheado. La comprobación que descarta las tres cuesta diez segundos y merece ser lo primero que hagas ante cualquier comportamiento raro de red: mira si hay un service worker registrado, y si lo hay, marca la opción de saltárselo para las peticiones de red, o desregístralo. La regla general que se extrae: el panel de red muestra lo que tu página pide, no necesariamente lo que sale de tu máquina, y esos dos conjuntos dejaron de coincidir el día en que los service workers se volvieron habituales.