Async

Escopo: TypeScript. Idiomas específicos deste ecossistema.

Os padrões assíncronos do JavaScript continuam valendo aqui: async/await, Promise.all para o que pode correr em paralelo, e um cliente de API (Application Programming Interface · Interface de Programação de Aplicações) centralizado no lugar de fetch espalhado. O que o TypeScript acrescenta é o tipo do que ainda não chegou. Uma Promise<T> (promessa tipada) declara no retorno o que vai estar dentro dela quando a operação terminar, e um generic (tipo paramétrico) deixa quem chama o cliente de I/O (Input/Output · Entrada/Saída) dizer qual resposta espera, sem recorrer a any.

Conceitos fundamentais

ConceitoO que é
I/O (Input/Output · Entrada/Saída)Operação que atravessa o limite do processo: rede, disco, banco
Promise<T> (promessa tipada)Objeto que representa o resultado futuro tipado de uma operação assíncrona
async/await (assíncrono/aguardar)Açúcar sobre Promises; permite escrever assíncrono com fluxo linear
generic (tipo paramétrico)Parâmetro de tipo (<T>) que leva ao retorno o formato de resposta que quem chama declarou
Awaited<T> (utilitário de desembrulho)Utilitário que extrai o tipo de dentro de uma Promise<T>
Promise.all (paralelismo tipado)Resolve um array de promises em paralelo; preserva tuple de tipos
Result<T, E> (resultado tipado)Padrão de retorno que carrega sucesso ou erro tipado, sem throw

A função async declara o que vem dentro da Promise

Toda função async devolve uma Promise, e o que importa para quem chama é o que está dentro dela. Promise<User | null> no retorno avisa que o usuário pode não existir, e o null é tratado na hora. Sem a anotação, essa informação só aparece para quem abrir a implementação.

❌ Ruim: a função async exportada não diz o que devolve
export async function findUserById(id: string) {
  const user = await db.users.findById(id);
  return user; // Promise<User | null>, mas o contrato não está visível
}
✅ Bom: Promise explícito
export async function findUserById(id: string): Promise<User | null> {
  const user = await userRepository.findById(id);
  return user;
}

export async function createOrder(input: CreateOrderInput): Promise<Order> {
  const order = await orderRepository.create(input);
  return order;
}

Chamadas independentes correm juntas, e os tipos sobrevivem

Três await em sequência esperam um pelo outro sem motivo: se as três chamadas não dependem do resultado uma da outra, a função demora a soma dos três tempos em vez do maior deles. Promise.all dispara as três de uma vez.

Do lado dos tipos, nada se perde. Promise.all devolve cada posição com o tipo que ela tinha, e a desestruturação no const [user, orders, invoices] chega com User, Order[] e Invoice[] nos lugares certos.

❌ Ruim: um await espera o outro sem depender dele
async function fetchDashboard(userId: string): Promise<Dashboard> {
  const orders = await fetchOrders(userId);     // espera terminar
  const invoices = await fetchInvoices(userId); // só começa depois
  const profile = await fetchProfile(userId);   // só começa depois

  const dashboard = { orders, invoices, profile };

  return dashboard;
}
✅ Bom: as três chamadas correm juntas, cada tipo no seu lugar
async function fetchDashboard(userId: string): Promise<Dashboard> {
  const [orders, invoices, profile] = await Promise.all([
    fetchOrders(userId),   // Promise<Order[]>
    fetchInvoices(userId), // Promise<Invoice[]>
    fetchProfile(userId),  // Promise<UserProfile>
  ]);
  // TypeScript infere: [Order[], Invoice[], UserProfile]

  const dashboard: Dashboard = { orders, invoices, profile };
  return dashboard;
}

Promise.allSettled retorna PromiseSettledResult<T>[]. Use quando quiser continuar mesmo com falhas parciais e precisar inspecionar cada resultado.

O cliente de API deixa quem chama declarar o tipo da resposta

Um cliente com get<T> e post<T> resolve um problema de posse da informação: o cliente não tem como saber o que cada rota devolve, mas quem chama sabe. O parâmetro de tipo transporta essa informação até o retorno, e o any deixa de ser necessário.

❌ Ruim: fetch direto, e o any se espalha a partir da resposta
// user.service.ts
async function fetchUser(id: string) {
  const response = await fetch(`https://api.example.com/users/${id}`, {
    headers: { Authorization: `Bearer ${token}` },
  });
  const user: any = await response.json(); // any: sem contrato

  return user;
}
✅ Bom: o cliente é genérico, e quem chama declara o tipo que espera
// api.client.ts
interface ApiClient {
  get<TResponse>(path: string): Promise<TResponse>;
  post<TBody, TResponse>(path: string, body: TBody): Promise<TResponse>;
}

function createApiClient(baseUrl: string, token: string): ApiClient {
  async function get<TResponse>(path: string): Promise<TResponse> {
    const response = await fetch(`${baseUrl}${path}`, {
      headers: { Authorization: `Bearer ${token}` },
    });

    const body = await response.json() as TResponse;
    return body;
  }

  async function post<TBody, TResponse>(path: string, body: TBody): Promise<TResponse> {
    const response = await fetch(`${baseUrl}${path}`, {
      method: "POST",
      headers: {
        Authorization: `Bearer ${token}`,
        "Content-Type": "application/json",
      },
      body: JSON.stringify(body),
    });

    const result = await response.json() as TResponse;
    return result;
  }

  const client: ApiClient = { get, post };
  return client;
}
// user.service.ts
async function fetchUser(apiClient: ApiClient, id: string): Promise<User> {
  const user = await apiClient.get<User>(`/users/${id}`);
  return user;
}

// order.service.ts
async function createOrder(apiClient: ApiClient, input: CreateOrderInput): Promise<Order> {
  const order = await apiClient.post<CreateOrderInput, Order>("/orders", input);
  return order;
}

O map com função async devolve promessas, não valores

map não sabe esperar. Ao receber uma função async, ele chama a função para cada item, recolhe o que ela devolveu (uma Promise) e monta um array delas. O resultado é um Promise<User>[], e qualquer tentativa de ler user.name ali dentro falha, porque o objeto ainda não chegou. Promise.all em volta do map resolve as promessas e devolve os valores.

❌ Ruim: o map devolve um array de promessas, e ninguém as aguarda
async function enrichOrders(orders: Order[]): Promise<EnrichedOrder[]> {
  const enriched = orders.map(async (order) => {
    const customer = await fetchCustomer(order.customerId);
    return { ...order, customer };
  });
  // enriched é Promise<EnrichedOrder>[], não EnrichedOrder[]

  return enriched; // erro de tipos
}
✅ Bom: Promise.all aguarda as promessas e entrega os valores
async function enrichOrders(orders: Order[]): Promise<EnrichedOrder[]> {
  const enrichmentTasks = orders.map(async (order) => {
    const customer = await fetchCustomer(order.customerId);

    const enrichedOrder: EnrichedOrder = { ...order, customer };
    return enrichedOrder;
  });

  const enrichedOrders = await Promise.all(enrichmentTasks);
  return enrichedOrders;
}

DoDocs v3.7.0 · Desenvolvido por @thiagocajadev · Baseado no trabalho de pmndrs/docs · Poimandres.