O tratamento de erros é uma parte essencial do desenvolvimento de software. Em JavaScript, erros podem ocorrer durante a execução do código, seja por problemas de sintaxe, dados inválidos ou falhas externas. Para lidar com essas situações de forma controlada, a linguagem oferece as instruções try, catch e finally. Nesta aula, vamos explorar como usar essas estruturas para capturar e gerenciar erros, evitando que o programa quebre inesperadamente.

Além disso, veremos como lançar erros personalizados com throw, entender o objeto Error e seus subtipos, e adotar boas práticas para um tratamento de erros eficiente e legível.

throw

A instrução throw permite que você lance uma exceção personalizada. Quando uma exceção é lançada, a execução normal do código é interrompida e o controle é transferido para o bloco catch mais próximo. Você pode lançar qualquer valor: números, strings, objetos, mas é recomendável usar o objeto Error ou suas subclasses para manter a consistência.

Exemplo básico:

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

try {
  let resultado = dividir(10, 0);
  console.log(resultado);
} catch (erro) {
  console.error('Erro capturado:', erro.message);
}

Neste exemplo, a função dividir verifica se o divisor é zero e lança um erro. O bloco try executa a função, e o catch captura o erro, exibindo a mensagem.

Objeto Error

O objeto Error é a base para todos os erros em JavaScript. Ele possui propriedades como name (tipo do erro) e message (descrição). Você pode criar instâncias personalizadas:

const erro = new Error('Algo deu errado');
console.log(erro.name);    // "Error"
console.log(erro.message); // "Algo deu errado"
console.log(erro.stack);   // stack trace (pilha de chamadas)

Além do Error genérico, existem subtipos como TypeError, RangeError, ReferenceError, etc. Eles herdam de Error e são usados para indicar tipos específicos de falhas.

Tipos de erro

JavaScript possui vários tipos de erro nativos, cada um indicando uma situação diferente:

  • SyntaxError: Erro de sintaxe no código (ex: falta de parênteses).
  • ReferenceError: Tentativa de acessar uma variável não definida.
  • TypeError: Operação incompatível com o tipo (ex: chamar algo que não é função).
  • RangeError: Valor fora do intervalo permitido (ex: array com tamanho negativo).
  • URIError: Erro em funções de URI como decodeURI().

Exemplo de TypeError:

try {
  let num = 42;
  num(); // TypeError: num is not a function
} catch (erro) {
  if (erro instanceof TypeError) {
    console.log('Erro de tipo:', erro.message);
  } else {
    console.log('Outro erro:', erro.message);
  }
}

É importante verificar o tipo do erro no catch para tratar cada situação adequadamente.

Boas práticas

Ao trabalhar com tratamento de erros, siga estas recomendações:

  • Sempre use objetos Error: Lance instâncias de Error ou subclasses, nunca strings ou números. Isso fornece informações como stack trace.
  • Seja específico: Crie classes de erro personalizadas para diferentes cenários (ex: ValidationError, DatabaseError).
  • Não engula erros: Evite blocos catch vazios. Sempre registre ou trate o erro de alguma forma.
  • Use finally para limpeza: O bloco finally executa independentemente de erro, ideal para fechar conexões ou liberar recursos.
  • Evite try/catch em código síncrono desnecessário: Use apenas onde erros são esperados e possam ser recuperados.

Exemplo com finally:

function processarArquivo() {
  let arquivo = null;
  try {
    arquivo = abrirArquivo('dados.txt');
    // processa...
  } catch (erro) {
    console.error('Erro ao processar arquivo:', erro);
  } finally {
    if (arquivo) {
      arquivo.fechar();
    }
  }
}

Referências

Exercícios

  1. Crie uma função que aceite um número e lance um erro se o valor for negativo. Use try/catch para testar.
  2. ✓ Resposta:
    function verificarPositivo(num) {
      if (num < 0) {
        throw new Error('Número não pode ser negativo');
      }
      return num;
    }
    
    try {
      console.log(verificarPositivo(-5));
    } catch (erro) {
      console.error(erro.message);
    }
    
  3. Escreva um código que force um ReferenceError e o trate exibindo uma mensagem personalizada.
  4. ✓ Resposta:
    try {
      console.log(variavelInexistente);
    } catch (erro) {
      if (erro instanceof ReferenceError) {
        console.log('Variável não definida:', erro.message);
      } else {
        throw erro;
      }
    }
    
  5. Crie uma classe de erro personalizada chamada ValidationError que herde de Error. Use-a em uma função de validação de email.
  6. ✓ Resposta:
    class ValidationError extends Error {
      constructor(message) {
        super(message);
        this.name = 'ValidationError';
      }
    }
    
    function validarEmail(email) {
      if (!email.includes('@')) {
        throw new ValidationError('Email inválido: falta @');
      }
      return true;
    }
    
    try {
      validarEmail('usuario.com');
    } catch (erro) {
      if (erro instanceof ValidationError) {
        console.error('Erro de validação:', erro.message);
      } else {
        console.error('Outro erro:', erro);
      }
    }
    
  7. Use o bloco finally para garantir que uma mensagem de "Fim da operação" seja sempre exibida, independentemente de erro.
  8. ✓ Resposta:
    try {
      console.log('Tentando executar...');
      throw new Error('Algo deu errado');
    } catch (erro) {
      console.error('Erro:', erro.message);
    } finally {
      console.log('Fim da operação');
    }
    
  9. Escreva um código que demonstre o uso de try/catch aninhado, capturando erros em diferentes níveis.
  10. ✓ Resposta:
    try {
      try {
        let obj = null;
        obj.prop; // TypeError
      } catch (erroInterno) {
        console.log('Erro interno capturado:', erroInterno.message);
        throw new Error('Erro relançado');
      }
    } catch (erroExterno) {
      console.log('Erro externo capturado:', erroExterno.message);
    }