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