Tratamento Avançado de Erros em TypeScript: do try/catch às Soluções Type-Safe
O tratamento de erros é um aspecto criticamente importante do desenvolvimento em TypeScript. O básico try/catch muitas vezes se mostra insuficiente para aplicações complexas. Neste artigo, veremos técnicas avançadas que ajudarão a tornar o código mais robusto, previsível e type-safe.
1. O Problema do try/catch Padrão: Perda de Tipos
A principal desvantagem do catch clássico é que a variável de erro tem o tipo unknown (ou any em versões antigas). Isso força o desenvolvedor a verificar manualmente o tipo:
try { riskyOperation();} catch (error) { // error: unknown if (error instanceof Error) { console.error(error.message); } else { console.error('Erro desconhecido', error); }}Solução: criar uma utilidade para captura tipada de erros:
function getErrorMessage(error: unknown): string { if (error instanceof Error) return error.message; if (typeof error === 'string') return error; if (error && typeof error === 'object' && 'message' in error) { return String((error as { message: unknown }).message); } return 'Ocorreu um erro desconhecido';}
try { riskyOperation();} catch (error) { console.error(getErrorMessage(error));}2. Classes de Erro Personalizadas com Dados Tipados
Criar uma hierarquia de erros permite identificar precisamente o problema e transmitir dados adicionais:
// Classe baseclass AppError extends Error { constructor( message: string, public readonly code: number, public readonly isOperational: boolean = true ) { super(message); this.name = 'AppError'; }}
// Erros especializadosclass ValidationError extends AppError { constructor( message: string, public readonly field: string, public readonly invalidValue: unknown ) { super(message, 400); this.name = 'ValidationError'; }}
class NotFoundError extends AppError { constructor(entity: string, id: string) { super(`${entity} com ID ${id} não encontrado`, 404, false); this.name = 'NotFoundError'; }}
// Usofunction findUser(id: string): User { if (!validateId(id)) { throw new ValidationError('ID inválido', 'id', id); } const user = db.findUser(id); if (!user) { throw new NotFoundError('Usuário', id); } return user;}
try { const user = findUser('123');} catch (error) { if (error instanceof NotFoundError) { // error.code === 404, error.message contém a descrição console.warn(error.message); } else if (error instanceof ValidationError) { // error.field e error.invalidValue estão disponíveis console.error(`Erro no campo ${error.field}: ${error.message}`); } else if (error instanceof AppError) { console.error(`Erro de aplicação (código ${error.code}): ${error.message}`); }}3. Discriminated Unions para Tratamento de Erros
Este padrão permite tratar resultados de operações sem exceções, usando uma união de tipos com discriminante:
// Tipo de resultado com discriminantetype Result<T, E = Error> = | { success: true; data: T } | { success: false; error: E };
// Função que retorna um Resultfunction safeDivide(a: number, b: number): Result<number, string> { if (b === 0) { return { success: false, error: 'Divisão por zero' }; } return { success: true, data: a / b };}
// Usoconst result = safeDivide(10, 0);
if (result.success) { // TypeScript sabe que result.data é number console.log(`Resultado: ${result.data}`);} else { // result.error é string console.error(`Erro: ${result.error}`);}
// Exemplo mais complexo com erros personalizadostype UserResult = Result<