Back

Corrigindo 'Maximum call stack size exceeded' em JavaScript

Corrigindo 'Maximum call stack size exceeded' em JavaScript

Você está olhando para o seu console e lá está: RangeError: Maximum call stack size exceeded. No Firefox, pode aparecer too much recursion. De qualquer forma, sua aplicação acabou de travar e você precisa entender o porquê—e como corrigir adequadamente.

Este erro sinaliza um estouro de pilha: seu código fez tantas chamadas de função aninhadas que o motor JavaScript ficou sem espaço para rastreá-las. Vamos explorar o que desencadeia isso no JavaScript moderno, como depurar efetivamente e como prevenir em aplicações React, Next.js e Node.js.

Pontos-Chave

  • A pilha de chamadas tem um tamanho finito que varia por ambiente, então nunca escreva código que dependa de atingir um limite específico.
  • Causas comuns incluem recursão infinita, recursão mútua, re-renderizações infinitas no React e referências circulares na serialização JSON.
  • Depure habilitando “Pausar em Exceções” no DevTools e procurando por padrões de função repetidos na pilha de chamadas.
  • Corrija estruturalmente convertendo recursão em iteração, usando trampolining ou tratando referências circulares com uma função replacer.

O Que a Pilha de Chamadas Realmente Faz

Toda vez que uma função é executada, o motor JavaScript cria um stack frame contendo as variáveis locais da função, argumentos e endereço de retorno. Esses frames se empilham uns sobre os outros. Quando uma função retorna, seu frame é removido.

O problema? Esta pilha tem um tamanho finito. O limite exato varia por ambiente—o Chrome pode permitir cerca de 10.000-15.000 frames, enquanto o Firefox permite aproximadamente 50.000. O Node.js normalmente limita em torno de 11.000 frames por padrão.

Importante: Esses números dependem da implementação e podem mudar entre versões. Não escreva código que dependa de atingir um limite específico.

Padrões Comuns Que Desencadeiam Estouro de Pilha

Recursão Infinita Clássica

O caso clássico: uma função que chama a si mesma sem uma condição de saída adequada.

function processItem(item) {
  // Falta caso base
  return processItem(item.child)
}

Recursão Mútua

Duas funções se chamando em um loop:

function isEven(n) {
  return n === 0 ? true : isOdd(n - 1)
}

function isOdd(n) {
  return n === 0 ? false : isEven(n - 1)
}

isEven(100000) // Estouro de pilha

Re-renderizações Infinitas no React

É aqui que muitos desenvolvedores frontend encontram o erro. Atualizações de estado durante a renderização criam loops infinitos:

function BrokenComponent() {
  const [count, setCount] = useState(0)
  setCount(count + 1) // Dispara re-renderização imediatamente
  return <div>{count}</div>
}

Dependências ruins no useEffect causam problemas similares:

useEffect(() => {
  setData(transformData(data)) // data muda, effect executa novamente
}, [data])

Estouro de Pilha por Referência Circular em JSON

Quando objetos referenciam a si mesmos, JSON.stringify recursa infinitamente:

const obj = { name: 'test' }
obj.self = obj
JSON.stringify(obj) // Maximum call stack size exceeded

Depurando Estouro de Pilha de Chamadas no Node.js e Navegadores

Passo 1: Habilite “Pausar em Exceções”

No Chrome DevTools, abra o painel Sources e habilite “Pause on caught exceptions”. Para Node.js, use a flag --inspect e conecte o Chrome DevTools.

Passo 2: Inspecione a Pilha de Chamadas em Busca de Frames Repetidos

Quando o depurador pausar, examine o painel da pilha de chamadas. Procure por padrões repetidos—a mesma função aparecendo dezenas ou centenas de vezes indica seu ponto de recursão.

Passo 3: Use Rastreamentos de Pilha Assíncronos

O DevTools moderno mostra rastreamentos de pilha assíncronos por padrão. Isso ajuda quando a recursão abrange cadeias de Promise ou callbacks setTimeout.

console.trace() // Imprime o rastreamento de pilha atual

Nota: Aumentar o tamanho da pilha com node --stack-size é uma ferramenta de diagnóstico, não uma solução. Isso atrasa a falha, mas não corrige o bug.

Correções Práticas Que Realmente Funcionam

Converta Recursão em Iteração

A maioria dos algoritmos recursivos pode se tornar iterativa com uma pilha explícita:

function processTree(root) {
  const stack = [root]
  while (stack.length > 0) {
    const node = stack.pop()
    process(node)
    if (node.children) {
      stack.push(...node.children)
    }
  }
}

Use Trampolining para Recursão Profunda

Trampolines quebram a recursão em etapas, prevenindo o crescimento da pilha:

function trampoline(fn) {
  return function(...args) {
    let result = fn(...args)
    while (typeof result === 'function') {
      result = result()
    }
    return result
  }
}

Trate Referências Circulares com Segurança

Para serialização JSON, use uma função replacer ou bibliotecas como flatted:

const seen = new WeakSet()
JSON.stringify(obj, (key, value) => {
  if (typeof value === 'object' && value !== null) {
    if (seen.has(value)) {
      return '[Circular]'
    }
    seen.add(value)
  }
  return value
})

Previna Recursão Infinita no React

Sempre garanta que as dependências do useEffect sejam estáveis e que as atualizações de estado sejam condicionais:

useEffect(() => {
  if (!isProcessed) {
    setData(transformData(rawData))
    setIsProcessed(true)
  }
}, [rawData, isProcessed])

Por Que a Otimização de Chamada de Cauda Não Vai Te Salvar

O ES6 especificou proper tail calls, que teoricamente permitiriam recursão infinita em posição de cauda. Na prática, apenas o Safari implementa isso. Chrome e Firefox não implementam, e o Node.js desabilitou. Não confie em TCO—refatore seu código em vez disso.

Conclusão

O erro “Maximum call stack size exceeded” é um bug de alta severidade que requer correções estruturais. Você não pode contorná-lo com try-catch, e não deveria tentar aumentar os limites de pilha em produção.

Encontre o padrão repetido no seu rastreamento de pilha, então adicione condições de terminação adequadas, converta para iteração ou divida o trabalho em blocos assíncronos. Trate referências circulares como problemas de estrutura de dados, não problemas de serialização.

Quando você vê este erro, seu código está dizendo que algo fundamental precisa mudar.

Perguntas Frequentes

Tecnicamente sim, mas não é uma solução confiável. No momento em que este erro é lançado, sua aplicação está em um estado instável. A pilha está esgotada e capturar o erro não a restaura. Corrija o problema de recursão subjacente em vez de tentar tratar a exceção.

Abra o DevTools do seu navegador, vá para o painel Sources e habilite Pausar em Exceções. Quando o erro ocorrer, examine o painel da pilha de chamadas em busca de nomes de função repetidos. A função que aparece repetidamente é a culpada. Você também pode usar console.trace() para imprimir a pilha em pontos específicos.

Chamar setState diretamente no corpo da renderização dispara uma re-renderização imediata, que chama setState novamente, criando um loop infinito. Mova as atualizações de estado para hooks useEffect com dependências adequadas, ou para manipuladores de eventos. Nunca atualize o estado incondicionalmente durante a renderização.

No Node.js, você pode usar a flag --stack-size para aumentar o limite, mas isso apenas atrasa a falha. Os navegadores não permitem alterações no tamanho da pilha. Nenhuma abordagem corrige a causa raiz. Refatore seu código para usar iteração ou padrões assíncronos em vez de depender de recursão profunda.

Complete picture for complete understanding

Capture every clue your frontend is leaving so you can instantly get to the root cause of any issue with OpenReplay — the open-source session replay tool for developers. Self-host it in minutes, and have complete control over your customer data.

Check our GitHub repo and join the thousands of developers in our community.

OpenReplay