Menu

try/catch em JavaScript: tratando erros sem quebrar o app

Como try/catch/finally funciona no JavaScript — capturando erros, o objeto de erro, rethrow e quando try/catch não é a ferramenta certa.

try/catch em JavaScript: uma rede de proteção, não um cinto de segurança

Quando uma linha de JavaScript lança um erro, a execução para na hora e o erro sobe pela call stack. Se ninguém capturar, o programa quebra (no Node) ou cospe aquela parede vermelha no console (no navegador). É aí que entra o try/catch: ele te deixa interceptar esse erro e dizer "olha, sei que isso pode falhar — quando falhar, faça isso aqui".

A estrutura básica é essa:

index.js
Output
Click Run to see the output here.

JSON.parse dispara um SyntaxError. A execução pula na hora para o bloco catch, com o erro disponível em err. O terceiro console.log ainda roda — a falha ficou contida.

Se o bloco try terminar sem lançar nada, o catch é simplesmente ignorado. Ele só existe para o caminho de erro.

O objeto Error do JavaScript

O que quer que seja lançado vai parar no parâmetro declarado em catch (...). Normalmente é uma instância de Error, que traz três campos úteis:

index.js
Output
Click Run to see the output here.

name indica qual subclasse do erro foi lançada (TypeError, RangeError, SyntaxError, etc. — falaremos mais sobre isso no próximo tópico). message é a descrição legível pra humanos. E stack é o rastreamento completo — uma mão na roda na hora de depurar.

Uma pegadinha importante: em JavaScript, você pode dar throw em qualquer coisa, não só em objetos Error. Código antigo às vezes faz coisas como throw "algo quebrou". Quando for escrever seu próprio throw, sempre lance um Error — assim quem chama a função recebe o stack trace junto:

index.js
Output
Click Run to see the output here.

finally sempre executa

O finally é um terceiro bloco opcional que roda independentemente de ter dado erro ou não, e independentemente do catch ter tratado a exceção. Ele serve para limpeza — fechar arquivos, liberar locks, esconder spinners de carregamento:

index.js
Output
Click Run to see the output here.

O spinner some tanto faz se o carregamento deu certo ou falhou. Sem o finally, você teria que escrever essa linha nos dois ramos — e esquecer de colocá-la em um deles.

O finally roda até mesmo se o bloco try ou catch tiver um return. A função só retorna depois que o finally é executado. Isso de vez em quando pega a gente de surpresa, mas na maioria das vezes é exatamente o comportamento desejado.

Nem sempre você precisa do catch

O catch é opcional. Um try/finally sem catch é válido e útil quando você quer garantir a limpeza, mas não tem intenção alguma de tratar o erro — você quer que ele se propague:

index.js
Output
Click Run to see the output here.

O try/finally interno libera o lock mesmo quando fn() lança uma exceção, mas sem engolir o erro — quem chamou continua enxergando ele. Engolir erros em silêncio ("falhou e eu não avisei ninguém") é um dos piores pesadelos na hora de debugar.

Relançar erro em JavaScript: trate alguns, propague o resto

Um bloco catch não precisa dar conta de tudo. Dá pra inspecionar o erro, tratar o que faz sentido e relançar o resto:

index.js
Output
Click Run to see the output here.

O padrão é usar o instanceof: reconheça os erros que você sabe como tratar e deixe o resto subir na pilha. Engolir qualquer erro com um bloco catch vazio é um baita code smell — você perde todo o sinal quando algo inesperado acontece.

try/catch com async/await em JavaScript

Dentro de uma função async, promises que dão reject no await viram erros lançados — e o try/catch lida com elas exatamente como faz com erros síncronos:

index.js
Output
Click Run to see the output here.

Vale destacar um detalhe sutil: o await precisa ficar dentro do bloco try. Se você retornar a promise sem usar await, a rejeição só acontece depois que a função já terminou de executar, e aí o catch nunca chega a vê-la:

async function bad() {
  try {
    return fetch("/broken");  // sem await — quem chamar verá a rejeição
  } catch (err) {
    // nunca executa
  }
}

Regra prática: dentro de funções async, coloque um await no que você quer que o try/catch cubra.

try/catch aninhado em JavaScript

Dá pra aninhar blocos try/catch quando o código interno e o externo podem falhar por motivos diferentes e você quer tratar cada caso de um jeito:

index.js
Output
Click Run to see the output here.

O catch interno trata o caso "o formato dos dados está errado" devolvendo um valor padrão seguro. Já o externo lida com "nem era JSON para começo de conversa", encapsulando e relançando o erro. Aninhar faz sentido quando cada camada tem uma estratégia de recuperação distinta — se os dois blocos fariam a mesma coisa, achate tudo em um só.

Quando não usar try/catch

O try/catch serve para falhas esperadas e recuperáveis. Ele não é uma forma de varrer bugs para debaixo do tapete.

  • Não envolva o corpo inteiro de uma função "só por garantia". Se você não tem um plano real para o erro, deixe ele subir — um erro não tratado com stack trace é bem mais útil do que um erro silenciado.
  • Não use para controle de fluxo. Blocos try têm custo real e deixam o código mais confuso do que um simples if. if (user) ganha de try { user.name } catch {} de lavada.
  • Não capture só para logar e ignorar. No mínimo, relance o erro ou devolva um valor sentinela que quem chamou consiga identificar.

O teste mental é: "o que quem usa esse código faz quando isso falha?". Se você não tem resposta, ainda não é hora de dar catch no erro.

Referência rápida

  • try { ... } catch (err) { ... } — intercepta erros lançados.
  • finally { ... } — sempre executa; use para limpeza.
  • throw new Error("...") — sempre lance subclasses de Error para o stack trace funcionar.
  • throw err; dentro do catch — relance quando não souber tratar.
  • await dentro do try — obrigatório para o try/catch enxergar rejeições de promises.

A seguir: tipos de Error

TypeError, RangeError, SyntaxError — o JavaScript tem toda uma família de classes de erro embutidas, e saber o que cada uma significa deixa a captura e o relato de erros muito mais precisos. É o tema do próximo documento.

Perguntas frequentes

Como funciona o try/catch no JavaScript?

Você coloca o código arriscado dentro de try { ... }. Se algo ali dentro lançar um erro, a execução pula direto para o bloco catch (err) { ... }, com o valor lançado amarrado em err. Se nada falhar, o catch é ignorado. Ainda tem o finally { ... } opcional, que roda de qualquer jeito — ótimo para limpeza de recursos.

Quando devo usar try/catch no JavaScript?

Use em operações que realmente podem falhar em tempo de execução: JSON.parse em entrada não confiável, respostas de fetch, I/O de arquivo ou rede. Não saia embrulhando cada linha — se você não tem plano nenhum para se recuperar do erro, deixe ele subir. Um try/catch largo em volta de código que funciona só esconde bugs em vez de tratá-los.

O try/catch captura erros assíncronos?

Só quando você usa await na promise dentro do try. Uma chamada solta tipo somePromise() não é capturada — o erro vira uma unhandled rejection. Com async/await, o try/catch se comporta igualzinho ao código síncrono. Para promises puras, use .catch() direto na cadeia.

Como relançar um erro no JavaScript?

Dentro do catch, basta um throw err; (ou lançar um erro novo que envolve o original). Isso ajuda quando você quer tratar alguns erros e deixar outros passarem — cheque o tipo ou a mensagem do erro, resolva o que dá, e faça rethrow do resto para que quem chamou lá em cima também enxergue.

Aprenda a programar com o Coddy

COMEÇAR