Voltar
Simplifique o tratamento de erros em TypeScript com este wrapper inteligente
Descubra como criar um wrapper TypeScript que elimina a repetição de try/catch e torna o tratamento de erros mais elegante e produtivo
Se você já desenvolveu em TypeScript, provavelmente já se cansou de escrever blocos try/catch
repetitivos em todo o seu código. Essa repetição não apenas torna o código verboso, mas também dificulta a manutenção e a legibilidade.
Neste artigo, vou mostrar como criar um wrapper TypeScript inteligente que simplifica drasticamente o tratamento de erros, tornando seu código mais limpo e produtivo.
O problema: Try/Catch repetitivo
Imagine que você tem várias funções que podem lançar erros:
async function buscarUsuario(id: string) {
try {
const response = await fetch(`/api/users/${id}`);
if (!response.ok) {
throw new Error("Usuário não encontrado");
}
return await response.json();
} catch (error) {
console.error("Erro ao buscar usuário:", error);
throw error;
}
}
async function criarPost(dados: PostData) {
try {
const response = await fetch("/api/posts", {
method: "POST",
headers: { "Content-Type": "application/json" },
body: JSON.stringify(dados),
});
if (!response.ok) {
throw new Error("Falha ao criar post");
}
return await response.json();
} catch (error) {
console.error("Erro ao criar post:", error);
throw error;
}
}
Repetitivo, não é? Vamos criar uma solução melhor.
A solução: Wrapper TypeScript simples
Vamos começar com uma versão mais simples e direta:
export const safeAwait = async <T>(
promise: Promise<T>
): Promise<[T | null, Error | null]> => {
try {
const data = await promise;
return [data, null];
} catch (error) {
return [null, error instanceof Error ? error : new Error(String(error))];
}
};
Como usar o safeAwait básico
Esta versão retorna uma tupla [dados, erro]
que é muito fácil de usar:
import { safeAwait } from "./safeAwait";
const buscarDados = async () => {
const [data, error] = await safeAwait(fetch("https://api.example.com/data"));
if (error) {
console.error("Erro ao buscar dados:", error);
return;
}
return data;
};
Exemplo prático com interface
interface Post {
userId: number;
id: number;
title: string;
body: string;
}
const buscarPosts = async (): Promise<Post[]> => {
const response = await fetch("https://jsonplaceholder.typicode.com/posts");
return response.json();
};
const buscarDados = async () => {
const [data, error] = await safeAwait(buscarPosts());
if (error) {
console.error("Erro ao buscar posts:", error);
return;
}
return data;
};
A solução: Wrapper TypeScript avançado
Para casos mais complexos, vamos criar um wrapper mais robusto que encapsula a lógica de tratamento de erros:
type Result<T, E = Error> =
| {
success: true;
data: T;
}
| {
success: false;
error: E;
};
async function tryCatch<T>(
fn: () => Promise<T> | T,
errorHandler?: (error: unknown) => void
): Promise<Result<T>> {
try {
const result = await fn();
return {
success: true,
data: result,
};
} catch (error) {
if (errorHandler) {
errorHandler(error);
}
return {
success: false,
error: error as Error,
};
}
}
Como usar o wrapper avançado
Com o wrapper avançado, suas funções ficam muito mais limpas:
async function buscarUsuario(id: string) {
return tryCatch(
async () => {
const response = await fetch(`/api/users/${id}`);
if (!response.ok) {
throw new Error("Usuário não encontrado");
}
return await response.json();
},
(error) => {
console.error("Erro ao buscar usuário:", error);
}
);
}
async function criarPost(dados: PostData) {
return tryCatch(
async () => {
const response = await fetch("/api/posts", {
method: "POST",
headers: { "Content-Type": "application/json" },
body: JSON.stringify(dados),
});
if (!response.ok) {
throw new Error("Falha ao criar post");
}
return await response.json();
},
(error) => {
console.error("Erro ao criar post:", error);
}
);
}
Usando o resultado
O wrapper avançado retorna um objeto Result
que você pode usar de forma type-safe:
async function exemploDeUso() {
const resultado = await buscarUsuario("123");
if (resultado.success) {
// TypeScript sabe que resultado.data existe aqui
console.log("Usuário encontrado:", resultado.data);
} else {
// TypeScript sabe que resultado.error existe aqui
console.log("Erro:", resultado.error.message);
}
}
Wrapper com configurações avançadas
Para casos mais complexos, você pode criar uma versão mais robusta:
interface TryCatchOptions {
onError?: (error: unknown) => void;
onSuccess?: <T>(data: T) => void;
retryCount?: number;
retryDelay?: number;
}
async function tryCatchAdvanced<T>(
fn: () => Promise<T> | T,
options: TryCatchOptions = {}
): Promise<Result<T>> {
const { onError, onSuccess, retryCount = 0, retryDelay = 1000 } = options;
for (let attempt = 0; attempt <= retryCount; attempt++) {
try {
const result = await fn();
if (onSuccess) {
onSuccess(result);
}
return {
success: true,
data: result,
};
} catch (error) {
if (onError) {
onError(error);
}
if (attempt === retryCount) {
return {
success: false,
error: error as Error,
};
}
// Aguarda antes da próxima tentativa
await new Promise((resolve) => setTimeout(resolve, retryDelay));
}
}
// Nunca deve chegar aqui, mas TypeScript exige
throw new Error("Unexpected error");
}
Exemplo com retry automático
async function buscarDadosComRetry() {
return tryCatchAdvanced(
async () => {
const response = await fetch("/api/dados");
if (!response.ok) {
throw new Error("Falha na requisição");
}
return await response.json();
},
{
retryCount: 3,
retryDelay: 2000,
onError: (error) => console.error("Tentativa falhou:", error),
onSuccess: (data) => console.log("Dados obtidos com sucesso:", data),
}
);
}
Wrapper para funções síncronas
Para funções que não são assíncronas:
function tryCatchSync<T>(
fn: () => T,
errorHandler?: (error: unknown) => void
): Result<T> {
try {
const result = fn();
return {
success: true,
data: result,
};
} catch (error) {
if (errorHandler) {
errorHandler(error);
}
return {
success: false,
error: error as Error,
};
}
}
Comparando as duas abordagens
safeAwait (Simples)
- Retorna: Tupla
[T | null, Error | null]
- Uso:
const [data, error] = await safeAwait(promise)
- Ideal para: Casos simples e diretos
- Vantagem: Sintaxe muito limpa e familiar
tryCatch (Avançado)
- Retorna: Objeto
Result<T>
com propriedadessuccess
,data
eerror
- Uso:
const result = await tryCatch(fn)
- Ideal para: Casos complexos com handlers personalizados
- Vantagem: Type safety mais robusto e flexibilidade
Qual escolher?
- Use safeAwait quando quiser uma solução rápida e simples
- Use tryCatch quando precisar de mais controle e funcionalidades avançadas
Benefícios do wrapper
- Código mais limpo: Elimina a repetição de try/catch
- Type safety: TypeScript garante que você trate os casos de sucesso e erro
- Flexibilidade: Fácil de customizar com handlers de erro
- Reutilização: Uma vez criado, pode ser usado em todo o projeto
- Manutenibilidade: Mudanças na lógica de tratamento de erros são centralizadas
Considerações finais
Este wrapper TypeScript é uma ferramenta poderosa que pode transformar a forma como você lida com erros em seus projetos. Ele não apenas reduz a repetição de código, mas também torna o tratamento de erros mais consistente e type-safe.
Para implementar em seu projeto, basta copiar o código do wrapper e começar a usar. Você verá imediatamente a diferença na legibilidade e manutenibilidade do seu código.
Lembre-se: o objetivo não é esconder erros, mas sim tratá-los de forma mais elegante e produtiva. O wrapper ainda permite que você capture e trate erros adequadamente, mas de uma forma muito mais limpa.
Experimente em seu próximo projeto e veja como isso pode melhorar sua experiência de desenvolvimento!
Créditos: Este artigo foi baseado no post original "Stop Repeating Try/Catch: This TypeScript Wrapper Makes Error Handling Easy" por Angel Serrano, traduzido e adaptado para português.