trilha

Tecnologias/01 - Linguagens/02 - TypeScript2 min

Promises, Async-Await e Erros

Definição

Uma Promise<T> representa uma conclusão futura que pode produzir um valor T ou falhar com uma rejeição. Uma função async sempre devolve uma Promise. await suspende a continuação daquela função até a Promise se resolver; a rejeição é tratada como uma exceção naquele ponto.

Isso não transforma cálculo pesado em trabalho paralelo: o código síncrono continua ocupando a thread em que executa. Referência: async function.

Exemplo com tratamento de erro

async function dobro(valor: number): Promise<number> {
  if (!Number.isFinite(valor)) {
    throw new Error("Valor deve ser finito");
  }
  return valor * 2;
}

async function executar(): Promise<void> {
  try {
    const total = await dobro(15);
    console.log(total); // 30
  } catch (erro: unknown) {
    console.error(erro instanceof Error ? erro.message : "Falha desconhecida");
  }
}

void executar();

O try/catch envolve o await para capturar a rejeição. Apenas chamar uma função assíncrona dentro de try, sem aguardar nem tratar a Promise retornada, não captura uma rejeição futura.

Esperar várias operações

Promise.all([operacaoA(), operacaoB()]) espera todas terem sucesso e preserva a ordem de entrada no array de resultados. Ele rejeita quando uma rejeita; as demais operações não são automaticamente canceladas.

Para operações independentes, iniciar ambas antes de aguardar evita somar esperas que poderiam se sobrepor. Quando a segunda depende do resultado da primeira, os await sequenciais representam essa dependência corretamente. Referência: Promise.all.

Limites

Promise<number> descreve o valor de sucesso, sem especificar o tipo da rejeição. Timeout, cancelamento e limite de operações simultâneas precisam ser implementados com os mecanismos do ambiente e da biblioteca usada.

Aprofundamento aplicado

Concorrência tem orçamento

Promise.all(ids.map(buscar)) inicia uma operação para cada ID quando buscar é chamada. Dez mil IDs podem gerar dez mil requisições pendentes. Um limite deve atuar na criação do trabalho, pois aguardar depois não desfaz o que já foi iniciado.

Um exercício simples usa lotes; suponha buscar(id) definida pela aplicação:

async function emLotes<T>(
  ids: string[], tamanho: number, buscar: (id: string) => Promise<T>,
): Promise<T[]> {
  if (!Number.isInteger(tamanho) || tamanho <= 0) throw new Error("Lote inválido");
  const saida: T[] = [];
  for (let i = 0; i < ids.length; i += tamanho) {
    saida.push(...await Promise.all(ids.slice(i, i + tamanho).map(buscar)));
  }
  return saida;
}

Esse desenho espera o item mais lento de cada lote antes de iniciar o próximo. Um pool de workers ocupa melhor as vagas livres, mas exige controlar fila, erros e encerramento. Se um lote rejeitar, requisições já iniciadas continuam até concluir ou ser canceladas por um mecanismo que elas suportem. Referência: comportamento de Promise.all.

Exercício: instrumente um contador de operações ativas e rode 12 IDs em lotes de 3. Resultado esperado: pico de no máximo 3; uma falha interrompe os lotes futuros, mas não cancela automaticamente o restante do lote atual.

Relacionado: 4. Unions, Narrowing e Valores Ausentes · Back Pressure.


Parte de TypeScript

Buscar

Busca por título, seção e texto das notas