Introdução: Por que o tratamento de erros em TypeScript não é apenas try/catch
TypeScript adiciona tipagem estática ao JavaScript, mas erros de tempo de execução (runtime errors) continuam sendo parte de qualquer aplicação. Ao contrário do JS puro, onde catch(e) retorna any, o TypeScript oferece mecanismos mais rigorosos. O tratamento adequado de erros não é apenas prevenir crashes, mas também criar um sistema compreensível para depuração e manutenção do código. Neste artigo, abordaremos todos os aspectos: desde o básico try/catch até a criação de hierarquias de erros personalizadas com tipagem completa.
1. Sintaxe básica do try/catch e o problema do tipo unknown
Construção clássica
A construção básica em TypeScript é igual à do JavaScript:
try {
// Código que pode lançar uma exceção
const data = JSON.parse('{"name": "TypeScript"}');
console.log(data.name);
} catch (error) {
// Tratamento do erro
console.error('Ocorreu um erro:', error);
}
No entanto, há uma diferença importante: em TypeScript, a variável error no bloco catch tem o tipo unknown (e não any, como em JavaScript, a menos que especificado de outra forma). Isso é feito para segurança de tipos.
O problema com unknown e como resolvê-lo
O tipo unknown significa que não podemos simplesmente acessar as propriedades do erro, por exemplo, error.message. O compilador emitirá um erro:
catch (error) {
// Erro TS2571: Object is of type 'unknown'.
console.log(error.message); // ❌ Não compilará
}
Para acessar as propriedades, precisamos restringir o tipo:
catch (error) {
if (error instanceof Error) {
console.log(error.message); // ✅ Funciona
} else {
console.log('Erro desconhecido:', error);
}
}
Ou usar uma verificação de existência da propriedade:
catch (error) {
if (error && typeof error === 'object' && 'message' in error) {
console.log((error as { message: string }).message);
}
}
2. Criando classes de erro personalizadas com tipagem
Por que isso é importante?
O Error padrão contém apenas name, message e stack. Em projetos reais, geralmente são necessários campos adicionais: código do erro, status HTTP, dados para logging. O TypeScript permite criar uma hierarquia de classes de erro com suporte total a tipos.
Exemplo de classe personalizada
// Classe base para todos os erros da aplicação
class AppError extends Error {
public readonly statusCode: number;
public readonly isOperational: boolean;
constructor(message: string, statusCode: number = 500, isOperational: boolean = true) {
super(message);
this.name = 'AppError';
this.statusCode = statusCode;
this.isOperational = isOperational;
// Restaura o protótipo (necessário para TypeScript)
Object.setPrototypeOf(this, new.target.prototype);
}
}
// Erros especializados
class NotFoundError extends AppError {
constructor(resource: string) {
super(`${resource} não encontrado`, 404);
this.name = 'NotFoundError';
}
}
class ValidationError extends AppError {
public readonly errors: string[];
constructor(errors: string[]) {
super('Erro de validação', 400);
this.name = 'ValidationError';
this.errors = errors;
}
}
Uso com try/catch
function getUser(id: number): User {
if (id <= 0) {
throw new ValidationError(['ID deve ser um número positivo']);
}
// ... lógica para obter o usuário
if (!user) {
throw new NotFoundError('Usuário');
}
return user;
}
try {
const user = getUser(-1);
} catch (error) {
if (err
```