Toda explicación de este error le dice lo mismo, cierto e inútil: una función se llamó a sí misma demasiadas veces, así que añada un caso base.

Eso ya lo sabía. Lo que quiere saber es qué llamada lo empezó, y este es el único error de JavaScript cuyo stack trace estructuralmente no puede decírselo.

Todo lo medido abajo se ejecutó en Chrome. Los números son los de V8; la forma se mantiene en cualquier motor que capture una pila en el momento de lanzar.

Qué contiene el trace en realidad

Tome una cadena en la que una función llama a otra, que llama a una tercera, que empieza a recurrir, y capture el error:

function entryPoint() { return level1() }
function level1()     { return level2() }
function level2()     { return recurse(0) }
function recurse(n)   { return recurse(n + 1) }

try { entryPoint() } catch (e) { console.log(e.stack) }

RangeError: Maximum call stack size exceeded, y luego diez líneas, todas y cada una recurse:

RangeError: Maximum call stack size exceeded
    at recurse (<anonymous>:2:145)
    at recurse (<anonymous>:2:156)
    at recurse (<anonymous>:2:156)
    ... seven more identical lines

entryPoint no está. Tampoco level1 ni level2. El trace nombra diez veces la función de la que ya sabía, y nada más.

Diez, porque Error.stackTraceLimit vale 10 por defecto.

Subir el límite no ayuda, y esta es la parte que conviene saber

El movimiento obvio es pedir más frames:

Error.stackTraceLimit = 200;

Hágalo y obtendrá 200 líneas. Medido sobre la cadena de arriba, las 200 son recurse, y entryPoint sigue ausente.

La razón está en desde dónde se captura el trace. Se toma en el momento de lanzar, que es el punto más profundo de la pila, y camina hacia atrás desde ahí. Su punto de entrada está en el otro extremo, a miles de frames. Pedir 200 frames le trae 200 frames del extremo equivocado.

Por eso aquí falla “lea el stack trace de abajo arriba”, que es la instrucción correcta para casi cualquier otro error. El fondo de este trace también es recurse.

Captúrelo temprano, y la ventana es estrecha

La información que quiere existe. Solo está disponible mientras la pila sigue siendo poco profunda.

Así que capture un trace desde dentro de la función que recurre, en una pasada temprana, y tire el error:

function recurse(n) {
  if (n === 2) console.log(new Error("where did this start").stack);
  return recurse(n + 1);
}

Sobre la cadena de arriba, ese trace termina así:

    at level2 (<anonymous>:4:27)
    at level1 (<anonymous>:4:27)
    at entryPoint (<anonymous>:3:31)

Que es la respuesta. La cadena entera, en orden, desde la llamada que la empezó.

La ventana es más o menos tan ancha como su límite de frames, y ahí está la trampa. Capturar a profundidad 2 da el punto de entrada. Capturar a profundidad 20, con el límite todavía en su valor por defecto de 10, no, porque a esas alturas los diez frames que el trace va a guardar ya han pasado del comienzo. Así que los dos movimientos funcionan juntos y ninguno funciona solo: suba Error.stackTraceLimit y capture en una pasada temprana.

Si no sabe qué función está recurriendo, el error sí le dice eso. Es el nombre repetido diez veces.

La versión que llega en un ticket

Todo lo anterior requiere que usted esté en el teclado mientras ocurre. Piense en qué le llega cuando le ocurre a otra persona.

La página se congeló, o se quedó en blanco, o un botón dejó de responder. Si esa persona abrió la consola y le hizo una foto, usted tiene diez líneas idénticas con el nombre de una función. Esa imagen no contiene ninguna información sobre qué camino del código se ejecutó, que es lo único que necesita.

Esto hace del desbordamiento de pila un caso raro entre los errores del navegador. Para la mayoría de los fallos la consola es el artefacto que conviene pedir, y para este es casi inservible: diez copias de un hecho que podría haber adivinado. Lo que de verdad acota es la secuencia de acciones inmediatamente anterior al congelamiento, porque el punto de entrada es algo que la persona hizo.

Lo cual es una inversión incómoda. El error con más salida en consola es el error en el que la salida de consola ayuda menos, y en el que lo que la persona estaba haciendo importa más.

Session Replay

Extensión gratuita para Chrome. Un clic en la página que está fallando captura la pantalla, la consola y el registro de red, y le entrega un enlace para pegar en el ticket.

Obtener la extensión

Qué quedarse de todo esto

El error es honesto y casi sin contenido: algo recurrió sin parar. Nombra la función del fondo del bucle y se calla la de arriba, no por tacañería, sino por el lugar desde el que se toma un stack trace.

Así que deje de leer el trace y vaya a conseguir otro, de una pasada temprana, con el límite subido. O, cuando sea el navegador de otra persona, deje de pedir la consola y pregunte qué pulsó.