Tipar Promise e Async/Await no TypeScript
Promise e async/await são o coração do código assíncrono moderno. Tipar corretamente garante que o compilador pega erros antes de chegar em produção. Veja como usar Promise<T>, tipar
Por que isso é importante
Tipar Promise e Async/Await no TypeScript. Promise e async/await são o coração do código assíncrono moderno. Tipar corretamente garante que o compilador pega erros antes de chegar em produção. Veja como usar Promise<T>, tipar retornos de funções async e aplicar Promise.all com segurança.
O Que É Promise<T> no TypeScript
Promise é um objeto que representa um valor que vai existir no futuro. No JavaScript puro, você cria uma Promise e ela resolve com qualquer coisa. No TypeScript, a gente coloca um tipo dentro do <T> pra dizer exatamente o que essa Promise vai retornar quando resolver.
Quando você escreve Promise<string>, tá dizendo: essa Promise vai resolver com uma string. Se alguém tentar usar o resultado como number, o compilador barra na hora. Simples assim.
O mesmo vale pra async/await. Toda função marcada com async retorna uma Promise automaticamente. Se a função retorna string, o tipo real é Promise<string>. O TypeScript sabe disso e te ajuda com autocomplete, validação e refatoração segura.
A parte boa é que, na maioria dos casos, o TypeScript consegue inferir o tipo da Promise pelo valor de retorno. Mas quando você tá trabalhando com APIs externas, dados vindos do banco ou bibliotecas sem tipagem, declarar o tipo explicitamente salva horas de debug.
Passo a Passo: Tipando Promise e Async/Await
Vamos construir o conhecimento em etapas. Cada passo adiciona uma camada de segurança ao seu código assíncrono.
Exemplos Práticos de Promise Tipada
Hora de ver código. Cada exemplo mostra um cenário real que você vai encontrar no dia a dia.
Promise Básica Tipada
// Promise que resolve com string
const promessaTexto: Promise<string> = new Promise((resolve) => {
setTimeout(() => {
resolve("Dados carregados");
}, 1000);
});
// Promise que resolve com número
const promessaNumero: Promise<number> = new Promise((resolve) => {
resolve(42);
});
// Usando o resultado
promessaTexto.then((texto) => {
console.log(texto.toUpperCase()); // OK, texto é string
});
Funções Async com Tipo de Retorno
interface Usuario {
id: number;
nome: string;
email: string;
}
// Tipo de retorno explícito: Promise<Usuario>
async function buscarUsuario(id: number): Promise<Usuario> {
const response = await fetch(`/api/usuarios/${id}`);
const data: Usuario = await response.json();
return data;
}
// O TypeScript sabe que 'user' é do tipo Usuario
const user = await buscarUsuario(1);
console.log(user.nome); // autocomplete funciona
console.log(user.email); // tudo tipado
Função Genérica para Fetch Tipado
// Função genérica que aceita qualquer tipo
async function fetchAPI<T>(url: string): Promise<T> {
const response = await fetch(url);
if (!response.ok) {
throw new Error(`HTTP error: ${response.status}`);
}
return response.json() as Promise<T>;
}
interface Produto {
id: number;
nome: string;
preco: number;
}
interface Pedido {
id: number;
total: number;
itens: Produto[];
}
// Cada chamada define o tipo retornado
const produto = await fetchAPI<Produto>("/api/produtos/1");
console.log(produto.preco); // tipo number
const pedido = await fetchAPI<Pedido>("/api/pedidos/1");
console.log(pedido.itens); // tipo Produto[]
Promise.all com Tipagem Automática
async function getUser(): Promise<Usuario> {
return { id: 1, nome: "Maria", email: "maria@email.com" };
}
async function getPosts(): Promise<string[]> {
return ["Post 1", "Post 2", "Post 3"];
}
async function getStats(): Promise<number> {
return 150;
}
// Promise.all infere uma tupla: [Usuario, string[], number]
const [user, posts, stats] = await Promise.all([
getUser(),
getPosts(),
getStats(),
]);
console.log(user.nome); // string
console.log(posts.length); // number
console.log(stats); // number
// Promise.allSettled - retorna status de cada Promise
const resultados = await Promise.allSettled([
getUser(),
getPosts(),
]);
resultados.forEach((r) => {
if (r.status === "fulfilled") {
console.log(r.value); // tipo inferido corretamente
} else {
console.log(r.reason); // erro
}
});
Try/Catch com Tipagem Segura
// O catch recebe 'unknown' por padrão no TypeScript
async function carregarDados(): Promise<Usuario | null> {
try {
const response = await fetch("/api/usuarios/1");
if (!response.ok) {
throw new Error(`Status: ${response.status}`);
}
return await response.json();
} catch (erro: unknown) {
// Precisa verificar o tipo antes de usar
if (erro instanceof Error) {
console.error("Mensagem:", erro.message);
console.error("Stack:", erro.stack);
} else {
console.error("Erro desconhecido:", erro);
}
return null;
}
}
// Padrão Result para evitar try/catch espalhado
type Result<T> =
| { ok: true; data: T }
| { ok: false; error: string };
async function fetchSeguro<T>(url: string): Promise<Result<T>> {
try {
const res = await fetch(url);
const data: T = await res.json();
return { ok: true, data };
} catch (e) {
return { ok: false, error: e instanceof Error ? e.message : "Erro" };
}
}
const resultado = await fetchSeguro<Produto>("/api/produtos/1");
if (resultado.ok) {
console.log(resultado.data.preco); // tipado como Produto
} else {
console.log(resultado.error); // string
}
O padrão Result é ouro puro pra projetos grandes. Em vez de try/catch em todo lugar, você centraliza o tratamento e o TypeScript te obriga a checar se deu certo antes de usar o dado. Isso elimina uma categoria inteira de bugs.
Erros Comuns com Promise no TypeScript
Armadilhas que pegam até dev experiente
Esquecer o await e trabalhar com a Promise em vez do valor: se você faz const user = buscarUsuario(1) sem await, user é Promise<Usuario>, não Usuario. O compilador nem sempre avisa porque Promise é um objeto válido. Sempre confira se tem await antes de usar o resultado.
Usar Promise<any> em funções de fetch: isso desliga toda a proteção de tipos no retorno. Crie interfaces pro dado que vem da API e use generics ou type assertions com as.
Não tratar o catch corretamente: o parâmetro do catch é unknown no modo strict. Tentar acessar erro.message direto causa erro de compilação. Sempre faça instanceof Error antes.
Misturar .then() com async/await sem necessidade: escolha um padrão e siga. Misturar os dois deixa o código confuso e dificulta a tipagem. Async/await é mais legível na maioria dos casos.
Ignorar Promise.allSettled quando precisa de resiliência: Promise.all rejeita tudo se uma Promise falha. Se você quer resultados parciais, use Promise.allSettled e verifique o status de cada resultado.
Checklist de Promise Tipada
Domine Código Assíncrono com TypeScript
Tipar Promises é o que separa um projeto amador de um profissional. No CrazyStack, todo o backend é construído com funções async tipadas, padrão Result e tratamento de erros sólido. Você não aprende teoria: constrói um SaaS completo com Node.js, React e TypeScript do zero ao deploy.
Se você quer escrever código assíncrono que não quebra em produção, esse é o caminho.