¿Qué significa “RangeError: Maximum call stack size exceeded” y cómo solucionarlo?
Un buen ingeniero, aparte de asegurarse que su código funciona, se esfuerza por entenderlo.
Si te gustaría entender el error “RangeError: Maximum call stack size exceeded”, lee las secciones ¿Qué es la Pila de Llamadas (Call Stack) de Javascript? y ¿Cuál es el límite de tamaño de la pila de llamadas?
Pero si solo quieres saber sobre las posibles causas del error y sus soluciones, puedes dirigirte a las secciones sobre el diagnóstico.
- Cuando una función se llama a sí misma
- Con un método getter o setter
- Cuidado con los bucles (loops) infinitos
- Asegúrate de no cargar el mismo script o la misma librería varias veces
El error “RangeError: Maximum call stack size exceeded” comúnmente sucede cuando una función se llama a sí misma indefinidamente. Este mismo error en Firefox aparece como “InternalError: too much recursion“.
Dicho de una manera más técnica, este error ocurre cuando se realiza una llamada de función que excede el tamaño de la Pila de Llamadas (Call Stack).
Ahora puede que te preguntes, ¿qué es la pila de llamadas (call stack) y cuál es su límite?
¡Excelente pregunta! Ahora te lo explico.
Tabla de Contenido
¿Qué es la Pila de Llamadas (Call Stack) de Javascript?
Otros nombres para la Pila de Llamadas son Pila de Ejecución, Pila de Control, Pila de Función, y en inglés, “Call Stack”.
Cuando un script ejecuta múltiples funciones, la pila de llamadas de Javascript es un mecanismo para que el intérprete se mantenga al tanto de qué función se está ejecutando actualmente y qué funciones serán llamadas dentro de esa función.
La Pila de Llamadas es una estructura de datos LIFO (Last In, First Out), es decir, “Último en Entrar, Primero en Salir”.
En una estructura de datos LIFO, la última función que se empuja a la pila es la primera que será llamada.
Imagina una pila de platos, en la cual los platos se colocan uno sobre el otro. Si deseas sacar uno, sacas primero el último plato que colocaste.
¿Cuál es el límite de tamaño de la pila de llamadas?
El tamaño límite de la pila de llamadas, depende del motor de Javascript que estés utilizando. Si tienes curiosidad por saber cuántas llamadas recursivas puede hacer cierto motor de Javascript, puedes ejecutar el siguiente código.
let i = 0;
function increment() {
i++;
increment();
}
try {
increment();
} catch (error) {
console.log(`El límite de tamaño de la pila de llamadas es ${i}.`);
}
En la versión del navegador Chrome que estoy usando la respuesta fue 45634
. Ejecuto el mismo código en la versión de Node de mi computadora y recibo la respuesta de 13962
.
Cuando una función se llama a sí misma
Si ves el error “RangeError: Maximum call stack size exceeded”, es probable que haya un problema con una función recursiva dentro de tu código. Específicamente, el problema radica en que la función se llama a sí misma indefinidamente.
He aquí un ejemplo. ¿Puedes identificar la falla en el código?
// cuentaRegresiva.js
let cuenta = 10;
function cuentaRegresiva() {
console.log(cuenta);
cuenta--;
cuentaRegresiva();
}
cuentaRegresiva();
Si ejecutamos este código veremos el error.
$ node cuentaRegresiva.js
...
-12429
-12430
internal/util/inspect.js:1298
function formatPrimitive(fn, value, ctx) {
^
RangeError: Maximum call stack size exceeded
Para solucionar el error cuando una función se llama a sí misma, tenemos que especificar una condición que lleva a que la función deje de llamarse a sí misma.
// cuentaRegresiva.js
let cuenta = 10;
function cuentaRegresiva() {
if (cuenta < 0) return;
console.log(cuenta);
cuenta--;
cuentaRegresiva();
}
cuentaRegresiva();
Ayuda a prevenir el síndrome de túnel carpiano al usar un Mouse ergonómico vertical. Consulta nuestro post ¿Cómo logré evitar el síndrome de túnel carpiano? para más información.
Con un método setter o getter
// setter.js
class Contacto {
constructor() {}
set nombre(nombre) {
this.nombre = nombre; // llamada recursiva
}
}
const contacto = new Contacto();
contacto.nombre = 'Leo';
Si ejecutamos este código veremos el error.
$ node setter.js
/Users/leo/Workspace/setter.js:5
this.nombre = nombre;
^
RangeError: Maximum call stack size exceeded
En este ejemplo, cuando se activa el setter, se le indica que vuelva a hacer lo mismo: establecer la misma propiedad que debe manejar. Esto hace que la función se llame a sí misma, una y otra vez, haciéndola infinitamente recursiva.
Este problema también aparece si se usa la misma variable en el getter.
He aquí un ejemplo de cómo puede ocurrir el error en un método getter. ¿Puedes identificar la falla en el código?
// getter.js
class Contacto {
get nombre(){
return this.nombre; // Llamada recursiva
}
}
const contacto = new Contacto();
console.log(contacto.nombre);
Si ejecutamos este código veremos el error.
$ node getter.js
/Users/leo/Workspace/getter.js:3
return this.nombre;
^
RangeError: Maximum call stack size exceeded
at get nombre [as nombre] (/Users/leo/Workspace/getter.js:3:17)
El getter nombre
se llama así mismo con this.nombre
lo que causa que se continúe llamando a sí mismo indefinidamente.
Cuidado con los bucles (loops) infinitos
Es posible generar el error de Maximum call stack size exceeded si tienes un bucle infinito que llama a una función.
// bucle-infinito.js
function suma(a, b) {
return a + b;
}
for (let i = 6; i > 5; i++) {
const resultado = suma(i, i);
console.log(resultado)
}
En este caso, la condición i > 5
siempre resultará true
y el bucle continuará indefinidamente. Cuando uses un bucle, ya sea for
, while
o algún otro, asegúrate especificar una condición que cause una salida.
Asegúrate de no cargar el mismo script o la misma librería varias veces
El error puede ocurrir si cargas el mismo archivo más de una vez, por ejemplo, al cargar la librería jQuery en la misma página varias veces.
Si estás programando con la plataforma de desarrollo Angular, por descuido podrías hacer un “import” circular de un módulo.
import { NgModule } from '@angular/core';
import { CommonModule } from '@angular/common';
@NgModule({
imports: [
CommonModule,
ModuleTwo
],
})
export class ModuleOne {
}
import { NgModule } from '@angular/core';
import { CommonModule } from '@angular/common';
@NgModule({
imports: [
CommonModule,
ModuleOne
],
})
export class ModuleTwo {
}
¿Puedes identificar el import circular? ModuleOne importa ModuleTwo y ModuleTwo importa ModuleOne.
Los errores “InternalError: too much recursion” y “RangeError: Maximum call stack size exceeded” por lo general ocurren cuando una función se llama a sí misma tantas veces que excede el límite de la pila de llamadas.
Para resolver el error, averigua la causa y especifica una condición que dé salida a la recursión.