Pular para o conteúdo
TypeScript

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, 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 pra dizer exatamente o que essa Promise vai retornar quando resolver.

Quando você escreve Promise, 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. 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.

  1. Passo 1 - Crie uma Promise tipada: Use new Promise((resolve, reject) => { ... }). O resolve só vai aceitar valores do tipo que você declarou. Se declarou Promise, passar uma string no resolve causa erro de compilação.
  2. Passo 2 - Tipe o retorno de funções async: Declare o tipo de retorno da função: async function buscarUsuario(): Promise. Isso garante que o await sempre entrega o tipo certo. Se a implementação retornar algo diferente, o compilador avisa.
  3. Passo 3 - Use generics quando o tipo varia: Crie funções genéricas como async function fetchData(url: string): Promise. Assim, cada chamada define o tipo esperado: fetchData('/api/produtos').
  4. Passo 4 - Tipe Promise.all e Promise.race: Promise.all recebe um array de Promises e retorna uma tupla. O TypeScript infere os tipos individualmente: const [user, posts] = await Promise.all([getUser(), getPosts()]). Cada variável recebe o tipo correto.
  5. Passo 5 - Trate erros com try/catch tipado: O catch no TypeScript recebe unknown por padrão. Antes de usar o erro, verifique com instanceof Error. Isso garante acesso seguro a message e stack.
  6. Passo 6 - Evite Promise e Promise quando não faz sentido: Promise desliga a checagem. Promise só serve quando a função não retorna dado útil, como salvar um log. Nos outros casos, tipe o retorno com precisão.

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, 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 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

  • Funções async têm tipo de retorno explícito: Promise
  • Interfaces criadas para dados vindos de APIs externas
  • Promise.all com desestruturação tipada nos resultados
  • Catch usa unknown e verifica instanceof Error
  • Nenhum Promise no código — trocado por tipos concretos
  • Funções genéricas para fetch reutilizável com tipagem
  • Promise.allSettled usado quando precisa de resiliência
  • Padrão Result implementado para tratamento centralizado de erros

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.