Javascript

Tratamento de Erros com try, catch e finally Já leu

9 min de leitura

Tratamento de Erros com try, catch e finally
Todo programa que vai para produção vai encontrar situações inesperadas. O usuário digita um valor inválido. A API n&a

Todo programa que vai para produção vai encontrar situações inesperadas. O usuário digita um valor inválido. A API não responde. O arquivo não existe. O banco de dados cai. Nenhum desses cenários é um bug — são situações previsíveis que precisam ser tratadas com elegância.

Um código que não trata erros quebra silenciosamente, exibe mensagens técnicas para o usuário ou simplesmente para de funcionar. Um código bem escrito antecipa o que pode dar errado e age de forma controlada.

É isso que vamos aprender neste artigo.


O que é um erro em JavaScript?

Quando o JavaScript encontra um problema que não consegue resolver, ele lança um objeto de erro e interrompe a execução do código — a menos que você capture esse erro.

// Sem tratamento — o programa quebra aqui
const resultado = JSON.parse("isso não é um JSON válido");
console.log("Esta linha nunca executa.");
// SyntaxError: Unexpected token i in JSON at position 0

O erro se propaga para cima na pilha de chamadas até encontrar alguém que o trate — ou até travar o programa.


try e catch — capturando erros

A estrutura básica do tratamento de erros:

try {
  // código que pode lançar um erro
  const dados = JSON.parse("json inválido");
} catch (erro) {
  // código executado SE um erro ocorrer
  console.log("Algo deu errado:", erro.message);
}

console.log("O programa continua normalmente.");
// Algo deu errado: Unexpected token j in JSON at position 0
// O programa continua normalmente.

O bloco try envolve o código arriscado. Se qualquer linha dentro dele lançar um erro, a execução pula imediatamente para o catch — que recebe o objeto de erro como parâmetro.


O objeto de erro

O erro capturado é um objeto com propriedades úteis:

try {
  null.propriedade; // TypeError
} catch (erro) {
  console.log(erro.name);    // TypeError
  console.log(erro.message); // Cannot read properties of null
  console.log(erro.stack);   // Stack trace completo (onde o erro ocorreu)
}

As propriedades mais usadas são name (tipo do erro) e message (descrição legível).


Tipos de erros nativos

O JavaScript possui vários tipos de erros embutidos, cada um para uma situação diferente:

// ReferenceError — variável não existe
try {
  console.log(variavelInexistente);
} catch (e) {
  console.log(e.name); // ReferenceError
}

// TypeError — tipo errado para a operação
try {
  null.metodo();
} catch (e) {
  console.log(e.name); // TypeError
}

// SyntaxError — código ou dado mal formado
try {
  JSON.parse("{chave sem aspas: valor}");
} catch (e) {
  console.log(e.name); // SyntaxError
}

// RangeError — valor fora do intervalo permitido
try {
  new Array(-1);
} catch (e) {
  console.log(e.name); // RangeError
}

finally — executar sempre

O bloco finally executa sempre, independente de ter ocorrido um erro ou não. É ideal para código de limpeza que deve rodar de qualquer jeito:

function lerArquivo(nome) {
  console.log(`Abrindo arquivo: ${nome}`);

  try {
    if (nome !== "dados.json") {
      throw new Error("Arquivo não encontrado.");
    }
    console.log("Arquivo lido com sucesso!");
    return "conteúdo do arquivo";
  } catch (erro) {
    console.log(`Erro: ${erro.message}`);
    return null;
  } finally {
    // executa sempre — com ou sem erro
    console.log("Fechando conexão com o arquivo.");
  }
}

lerArquivo("dados.json");
// Abrindo arquivo: dados.json
// Arquivo lido com sucesso!
// Fechando conexão com o arquivo.

lerArquivo("outro.txt");
// Abrindo arquivo: outro.txt
// Erro: Arquivo não encontrado.
// Fechando conexão com o arquivo.

Na prática, finally é muito usado para fechar conexões com banco de dados, esconder indicadores de carregamento ou liberar recursos.


throw — lançando seus próprios erros

Você não precisa esperar o JavaScript lançar um erro — pode lançar os seus próprios com throw:

function dividir(a, b) {
  if (b === 0) {
    throw new Error("Divisão por zero não é permitida.");
  }
  return a / b;
}

try {
  console.log(dividir(10, 2));  // 5
  console.log(dividir(10, 0));  // lança erro
} catch (erro) {
  console.log(`Erro capturado: ${erro.message}`);
}
// 5
// Erro capturado: Divisão por zero não é permitida.

Você pode lançar qualquer valor com throw — mas a convenção é sempre lançar um objeto Error para manter consistência.


Criando erros personalizados

Para sistemas maiores, é útil criar tipos de erro específicos estendendo a classe Error:

class ErroValidacao extends Error {
  constructor(campo, mensagem) {
    super(mensagem);
    this.name = "ErroValidacao";
    this.campo = campo;
  }
}

class ErroAutenticacao extends Error {
  constructor(mensagem) {
    super(mensagem);
    this.name = "ErroAutenticacao";
  }
}

function validarEmail(email) {
  if (!email.includes("@")) {
    throw new ErroValidacao("email", `"${email}" não é um e-mail válido.`);
  }
  return true;
}

function autenticar(usuario, senha) {
  if (senha.length < 6) {
    throw new ErroAutenticacao("Senha deve ter pelo menos 6 caracteres.");
  }
  return true;
}

// Tratando erros de tipos diferentes
function processarLogin(email, senha) {
  try {
    validarEmail(email);
    autenticar(email, senha);
    console.log("Login realizado com sucesso!");
  } catch (erro) {
    if (erro instanceof ErroValidacao) {
      console.log(`Campo inválido (${erro.campo}): ${erro.message}`);
    } else if (erro instanceof ErroAutenticacao) {
      console.log(`Falha de autenticação: ${erro.message}`);
    } else {
      console.log(`Erro inesperado: ${erro.message}`);
    }
  }
}

processarLogin("emailsemarroba.com", "123456");
// Campo inválido (email): "emailsemarroba.com" não é um e-mail válido.

processarLogin("user@email.com", "123");
// Falha de autenticação: Senha deve ter pelo menos 6 caracteres.

processarLogin("user@email.com", "senha123");
// Login realizado com sucesso!

Validação defensiva — errar cedo e com clareza

Um princípio de código limpo: valide as entradas no início da função e lance erros claros antes de continuar:

function criarUsuario({ nome, email, idade }) {
  // Valide tudo antes de qualquer processamento
  if (!nome || typeof nome !== "string") {
    throw new Error("Nome é obrigatório e deve ser texto.");
  }
  if (!email || !email.includes("@")) {
    throw new Error("E-mail inválido.");
  }
  if (!Number.isInteger(idade) || idade < 0 || idade > 130) {
    throw new Error("Idade deve ser um número inteiro entre 0 e 130.");
  }

  // Só chegamos aqui se tudo estiver válido
  return {
    id: Math.random().toString(36).slice(2),
    nome: nome.trim(),
    email: email.toLowerCase(),
    idade,
    criadoEm: new Date().toISOString(),
  };
}

try {
  const usuario = criarUsuario({ nome: "  Ana  ", email: "ana@email.com", idade: 25 });
  console.log(usuario);
} catch (erro) {
  console.log(`Erro ao criar usuário: ${erro.message}`);
}

Erros silenciosos — o que evitar

Um anti-padrão perigoso é capturar erros e não fazer nada com eles:

// ❌ Nunca faça isso — engole o erro silenciosamente
try {
  operacaoArriscada();
} catch (e) {
  // silêncio total
}

// ❌ Também ruim — captura mas não trata corretamente
try {
  operacaoArriscada();
} catch (e) {
  console.log("deu erro"); // sem detalhes, sem ação
}

// ✅ Trate com intenção
try {
  operacaoArriscada();
} catch (e) {
  console.error(`[ERRO] ${e.name}: ${e.message}`);
  // notificar sistema de monitoramento
  // exibir mensagem útil ao usuário
  // tentar uma alternativa (fallback)
}

Boas práticas de tratamento de erros

// ✅ 1. Seja específico — não trate tudo como erro genérico
catch (erro) {
  if (erro instanceof TypeError) { /* ... */ }
  if (erro instanceof RangeError) { /* ... */ }
}

// ✅ 2. Mensagens de erro úteis para o desenvolvedor
throw new Error(`Usuário com id ${id} não encontrado na base de dados.`);

// ✅ 3. Não lance erros para fluxo de controle normal
// Se "não encontrar" é esperado, retorne null — não lance erro
function buscarUsuario(id) {
  const usuario = banco.find(u => u.id === id);
  return usuario || null; // não lance erro para ausência esperada
}

// ✅ 4. Sempre use finally para limpeza de recursos
try {
  conexao.abrir();
  conexao.executar(query);
} catch (erro) {
  console.error(erro);
} finally {
  conexao.fechar(); // fecha sempre, com ou sem erro
}

Exemplo completo — sistema de cadastro

class ErroCadastro extends Error {
  constructor(mensagem, campo = null) {
    super(mensagem);
    this.name = "ErroCadastro";
    this.campo = campo;
  }
}

function validarCadastro(dados) {
  const { nome, email, senha, confirmacaoSenha } = dados;

  if (!nome || nome.trim().length < 2) {
    throw new ErroCadastro("Nome deve ter pelo menos 2 caracteres.", "nome");
  }
  if (!email || !email.includes("@")) {
    throw new ErroCadastro("E-mail inválido.", "email");
  }
  if (!senha || senha.length < 8) {
    throw new ErroCadastro("Senha deve ter pelo menos 8 caracteres.", "senha");
  }
  if (senha !== confirmacaoSenha) {
    throw new ErroCadastro("As senhas não coincidem.", "confirmacaoSenha");
  }

  return true;
}

function cadastrarUsuario(dados) {
  console.log("Iniciando cadastro...");

  try {
    validarCadastro(dados);
    console.log(`Usuário "${dados.nome}" cadastrado com sucesso!`);
    return { sucesso: true };
  } catch (erro) {
    if (erro instanceof ErroCadastro) {
      console.log(`Erro no campo "${erro.campo}": ${erro.message}`);
    } else {
      console.log(`Erro inesperado: ${erro.message}`);
    }
    return { sucesso: false, erro: erro.message };
  } finally {
    console.log("Processo de cadastro finalizado.\n");
  }
}

cadastrarUsuario({ nome: "A", email: "email@ok.com", senha: "12345678", confirmacaoSenha: "12345678" });
// Iniciando cadastro...
// Erro no campo "nome": Nome deve ter pelo menos 2 caracteres.
// Processo de cadastro finalizado.

cadastrarUsuario({ nome: "João", email: "emailinvalido", senha: "12345678", confirmacaoSenha: "12345678" });
// Iniciando cadastro...
// Erro no campo "email": E-mail inválido.
// Processo de cadastro finalizado.

cadastrarUsuario({ nome: "João", email: "joao@email.com", senha: "minhasenha", confirmacaoSenha: "minhasenha" });
// Iniciando cadastro...
// Usuário "João" cadastrado com sucesso!
// Processo de cadastro finalizado.

Tarefa para você

Construa uma função calcularMedia(notas) robusta que:

  1. Lance um erro se notas não for um array
  2. Lance um erro se o array estiver vazio
  3. Lance um erro se alguma nota não for um número entre 0 e 10
  4. Retorne a média calculada se tudo estiver correto
  5. Trate todos os erros com mensagens claras e específicas
// Esperado:
calcularMedia([8, 7, 9]);          // Média: 8.00
calcularMedia([]);                  // Erro: array vazio
calcularMedia("não sou um array"); // Erro: notas deve ser um array
calcularMedia([8, 15, 7]);         // Erro: nota 15 está fora do intervalo

Conclusão

Neste artigo você aprendeu:

  • O que acontece quando um erro não é tratado
  • Como usar try, catch e finally
  • As propriedades do objeto de erro
  • Os tipos nativos de erro do JavaScript
  • Como lançar erros com throw
  • Como criar classes de erro personalizadas
  • Validação defensiva e boas práticas
  • O que nunca fazer com erros

No próximo artigo encerramos o Módulo 1 com uma revisão completa e um mini projeto prático — uma calculadora no console que usa tudo que aprendemos até aqui.


📚 Fontes e Referências

Comentários

Mais em Javascript

Formulários: validação e coleta de dados
Formulários: validação e coleta de dados

Formul&aacute;rios s&atilde;o a principal forma de comunica&ccedil;&atilde;o...

Condicionais: if, else e switch
Condicionais: if, else e switch

No Javascript n&oacute;s guardamos informa&ccedil;&otilde;es em vari&aacute;v...

Mini Projeto: Calculadora no Console
Mini Projeto: Calculadora no Console

Chegamos ao fim do primeiro m&oacute;dulo. Em nove artigos voc&ecirc; percorr...