kb.erickguedes.com
JavaScript: Fundamentos para Web

Tratamento de Erros e Debugging

Aula 7 de 8

Tratamento de Erros

try/catch/finally

try {
  const resultado = operacaoRiscada();
  console.log('Sucesso:', resultado);
} catch (erro) {
  console.error('Erro capturado:', erro.message);
  console.error('Stack:', erro.stack);
} finally {
  console.log('Sempre executa, com ou sem erro');
}

Error e Throw

// Lançar erro
function dividir(a, b) {
  if (b === 0) {
    throw new Error('Divisão por zero não permitida');
  }
  if (typeof a !== 'number' || typeof b !== 'number') {
    throw new TypeError('Ambos os parâmetros devem ser números');
  }
  return a / b;
}

try {
  console.log(dividir(10, 0));
} catch (erro) {
  if (erro instanceof TypeError) {
    console.error('Erro de tipo:', erro.message);
  } else {
    console.error('Erro:', erro.message);
  }
}

Custom Error Classes

class ValidationError extends Error {
  constructor(campo, mensagem) {
    super(mensagem);
    this.name = 'ValidationError';
    this.campo = campo;
    this.timestamp = new Date().toISOString();
  }
}

class NotFoundError extends Error {
  constructor(recurso, id) {
    super(`${recurso} com id ${id} não encontrado`);
    this.name = 'NotFoundError';
    this.statusCode = 404;
  }
}

function buscarUsuario(id) {
  if (typeof id !== 'number') {
    throw new ValidationError('id', 'ID deve ser um número');
  }
  const usuario = banco.find(u => u.id === id);
  if (!usuario) {
    throw new NotFoundError('Usuário', id);
  }
  return usuario;
}

Tratamento de Erros Assíncronos

// Async/await com try/catch
async function carregarDados() {
  try {
    const response = await fetch('https://api.exemplo.com/dados');
    if (!response.ok) {
      throw new Error(`HTTP ${response.status}`);
    }
    return await response.json();
  } catch (erro) {
    console.error('Falha ao carregar dados:', erro);
    throw erro; // repassar
  }
}

// Caso de uso real: retry com fallback
async function fetchComRetry(url, tentativas = 3) {
  for (let i = 0; i < tentativas; i++) {
    try {
      const response = await fetch(url);
      if (!response.ok) throw new Error(`Status ${response.status}`);
      return await response.json();
    } catch (erro) {
      if (i === tentativas - 1) throw erro;
      console.warn(`Tentativa ${i + 1} falhou. Tentando novamente...`);
      await new Promise(r => setTimeout(r, 1000 * (i + 1)));
    }
  }
}

Debugging

Console API

// Básico
console.log('Mensagem simples');
console.error('Erro vermelho');
console.warn('Aviso amarelo');
console.info('Informação');

// Estruturado
console.table([
  { nome: 'Ana', idade: 25 },
  { nome: 'João', idade: 30 }
]);

// Grupo
console.group('Validação');
console.log('Campo nome: OK');
console.log('Campo email: INVÁLIDO');
console.groupEnd();

// Tempo de execução
console.time('loop');
for (let i = 0; i < 1000000; i++) {}
console.timeEnd('loop');

// Stack trace
console.trace('Rastreando chamada');

// Estilo
console.log('%cErro crítico', 'color: red; font-size: 20px; font-weight: bold');

Breakpoints (DevTools)

// debugger - pausa execução
function calcularTotal(itens) {
  debugger; // DevTools pausa aqui
  return itens.reduce((acc, item) => {
    return acc + item.preco * item.quantidade;
  }, 0);
}
# DevTools Tips:
# F12 > Sources > Abrir arquivo > Clique no número da linha
# Watch: adicione variáveis para monitorar
# Call Stack: veja a pilha de chamadas
# Scope: veja variáveis locais e closure

Strict Mode

'use strict';

// Erros que strict mode pega:
// variável sem declaração
// nome = 'João'; // ReferenceError!

// duplicar parâmetros
// function soma(a, a) {} // SyntaxError!

// deletar propriedade não configurável
// delete Object.prototype; // TypeError!

// this global undefined (em vez de window)
function mostrarThis() {
  console.log(this); // undefined (strict)
}

ESLint

# Instalar
npm init -y
npm install --save-dev eslint

# Iniciar configuração
npx eslint --init

# Verificar arquivos
npx eslint src/
npx eslint src/app.js --fix
// .eslintrc.json (exemplo)
{
  "env": {
    "browser": true,
    "es2022": true,
    "node": true
  },
  "extends": "eslint:recommended",
  "rules": {
    "no-unused-vars": "warn",
    "no-console": "off",
    "eqeqeq": "error",
    "curly": "error"
  }
}

Source Maps

<!-- Source maps permitem debugar código original no DevTools -->
<!-- Arquivo .map gerado pelo bundler -->
<script src="app.bundle.js"></script>
<!-- O navegador carrega app.bundle.js.map automaticamente -->

Lab: Validador com Erros Customizados

Crie uma função que valida dados de formulário e lança erros customizados com campos específicos. Use try/catch e console.table.

node validador.js
class ValidationError extends Error {
  constructor(campos) {
    super('Erro de validação');
    this.name = 'ValidationError';
    this.campos = campos; // array de { campo, mensagem }
  }
}

function validarUsuario(dados) {
  const erros = [];

  if (!dados.nome || dados.nome.length < 3) {
    erros.push({ campo: 'nome', mensagem: 'Mínimo 3 caracteres' });
  }
  if (!dados.email?.includes('@')) {
    erros.push({ campo: 'email', mensagem: 'E-mail inválido' });
  }
  if (!dados.idade || dados.idade < 18) {
    erros.push({ campo: 'idade', mensagem: 'Deve ser maior de 18 anos' });
  }

  if (erros.length) throw new ValidationError(erros);
  return dados;
}

try {
  validarUsuario({ nome: 'An', email: 'invalido' });
} catch (erro) {
  if (erro instanceof ValidationError) {
    console.table(erro.campos);
  } else {
    console.error(erro);
  }
}

Erros não são falhas — são informações. Trate-os adequadamente, crie classes de erro específicas e nunca deixe uma Promise sem catch.