← JavaScript: linguagem e diagnóstico de aplicações
07 / 10 · 55 MIN

Promises, async e propagação de erros

Relaciona promises, dependências e resultados; identifica trabalho que escapa à cadeia e distingue prazos de cancelamento.

Valores futuros e fluxo local

Uma função async devolve uma promise em cada chamada. Um return normal fornece o valor com que essa promise será resolvida; uma exceção não tratada rejeita-a. Mesmo async function count() { return 4; } devolve uma promise, apesar de o corpo não conter await. Não confundas o valor futuro com a própria promise.

O código do corpo começa a executar quando a função é chamada e avança até suspender num await ou terminar. Await permite retomar depois de a dependência produzir um resultado, sem bloquear toda a execução do ambiente. A rejeição retoma como throw naquele ponto. Esta linguagem de dependências não garante que trabalho intensivo de CPU passe automaticamente para outra thread. Uma função marcada async pode ainda executar trabalho síncrono demorado antes da primeira suspensão.

O retorno liga o trabalho à cadeia

Then cria uma promise derivada. Se o handler devolve um valor comum, esse valor resolve a derivada; se lança, a derivada é rejeitada; se devolve uma promise, a derivada acompanha o resultado dessa dependência. Um handler de rejeição também pode recuperar: devolver um valor normal torna o passo seguinte um sucesso. Decide se essa recuperação respeita o contrato, em vez de usar sempre uma lista vazia.

Em start().then(() => { save(); }).then(showDone), o primeiro handler devolve undefined. A chamada save foi iniciada, mas a sua promise não pertence ao resultado devolvido pelo handler. showDone pode executar antes de save terminar. Devolver save() estabelece a dependência. A análise deve seguir cada operação iniciada até ao chamador que observa o seu resultado, incluindo caminhos de rejeição.

Dependências, concorrência e limites

Se duas leituras precisam primeiro de um plano, aguarda o plano antes de as iniciar. Se depois são independentes, podem começar juntas. Promise.all conserva a posição de cada input no array de sucesso, independentemente da ordem em que as operações terminam. Uma rejeição rejeita a agregação, mas não cancela automaticamente as outras operações.

forEach chama callbacks e descarta os seus retornos. await ids.forEach(async id => save(id)) aguarda o undefined devolvido por forEach, não as gravações. Para um conjunto pequeno com concorrência permitida, map recolhe as promises e Promise.all acompanha o sucesso conjunto. Para listas grandes, iniciar tudo de uma vez pode exceder a capacidade do serviço: é necessário um limite de operações em curso definido pela aplicação. Agregação não é limitação de concorrência.

Sucesso conjunto, resultados individuais e prazo

Escolhe o combinador pelo resultado necessário. Promise.all serve uma operação que precisa de todos os sucessos. Promise.allSettled permite construir um relatório com um registo por input, distinguindo status fulfilled com value e status rejected com reason. Com um array comum de promises, espera por todas e conserva a ordem de entrada. Isto não significa que qualquer uso possível de allSettled esteja imune a erros de preparação ou iteração.

Promise.race determina a sua promise pelo primeiro input que a resolve ou rejeita. Se esse input representa um prazo, só sabes que a espera agregada terminou. O trabalho restante pode continuar. Não deduzas cancelamento, ausência de efeitos nem autorização para repetir uma escrita. Uma repetição exige uma regra de identidade e reconciliação fornecida pela aplicação; a biblioteca de promises não cria essa regra.

Catch, await e limpeza

Um try apanha throws que ocorrem na avaliação que envolve. Devolver diretamente uma promise rejeitada dentro de try não faz o catch local observar o motivo: a função devolve um resultado assíncrono que o chamador recebe como rejeição. Return await dessa promise permite que a rejeição retome como throw dentro do try. Usa esta distinção quando a função tem responsabilidade explícita de traduzir ou recuperar a falha.

O callback de finally é chamado sem argumentos. Se termina normalmente, conserva o valor ou motivo anterior, mesmo que devolva um valor comum diferente. Se lança ou devolve uma promise rejeitada, a limpeza pode substituir o resultado anterior por uma nova rejeição. Uma limpeza que pode falhar precisa de uma política clara para preservar contexto; finally não é uma garantia de que a falha original será sempre a observada pelo chamador.

Exemplo orientado: preparar uma estimativa

No exemplo buildEstimate, começa por marcar as fronteiras: readPlan fornece o ID; readUnits e readRate usam esse ID e podem coexistir; a estimativa só é criada depois de ambas terem sucesso e passarem os limites. Com plan.id igual a P7, units 3 e rate 8, o objeto final contém planId P7 e total 24. Se rate for a string "8", a validação rejeita; não há conversão implícita.

Se readPlan é rejeitada, as duas leituras seguintes não começam. Se readRate é rejeitada depois de ambas começarem, buildEstimate é rejeitada e não devolve uma estimativa, mas readUnits pode continuar. Os limites inteiros de 0 a 1000 mantêm o produto dentro de um intervalo exato pequeno. O código não publica numa interface nem escreve num serviço; o chamador deve aguardar o resultado e tratar a rejeição antes de decidir o que apresentar.

Diagnosticar a ordem sem inventar garantias

Uma reação then é agendada mesmo quando a promise já está cumprida. Na sequência que insere A, regista um then para inserir B e insere C sincronicamente, a ordem final é A, C, B. Esta previsão depende apenas da sequência e da execução posterior da reação, não de um número fixo de milissegundos. Não extrapoles daí uma ordem universal entre temporizadores, rede e pintura do navegador.

Ao rever uma cadeia, pergunta que trabalho cada promise representa, onde é observado o seu erro e se há efeitos que podem sobreviver à rejeição. Nas perguntas, distingue um valor errado de uma dependência ausente. Nos casos, escolhe a ordem que respeita os dados prévios e a política de publicação. A avaliação usa leitura e escolha de alternativas; não exige executar os excertos para obter a resposta.

async function buildEstimate(readPlan, readUnits, readRate) {
  const plan = await readPlan();
  if (plan === null || typeof plan !== "object" || Array.isArray(plan) ||
      typeof plan.id !== "string" || plan.id.length === 0) {
    throw new TypeError("plan");
  }
  const [units, rate] = await Promise.all([
    readUnits(plan.id),
    readRate(plan.id)
  ]);
  if (!Number.isInteger(units) || units < 0 || units > 1000 ||
      !Number.isInteger(rate) || rate < 0 || rate > 1000) {
    throw new TypeError("estimate inputs");
  }
  return { planId: plan.id, total: units * rate };
}
NA PRÁTICA

Lê o exemplo como um contrato, sem o executar. As três funções fornecidas devolvem promises padrão e representam leituras; o plano é um registo comum, estável, sem getters ou alterações concorrentes. readUnits e readRate só podem começar depois de receberem o ID do plano e são independentes entre si.

Armadilhas comuns

Omitir return de uma operação iniciada em then; usar await forEach como agregação; confundir ordem de entrada com ordem de conclusão; esperar que race ou all desfaçam trabalho.

Tópicos relacionados: Variáveis, funções e closures · Erros, validação e contratos de falha · Eventos, HTTP e cancelamento no navegador · Estado, ordem e diagnóstico de integração

Leva esta ideia contigo

Uma promise deve representar todo o trabalho que o chamador precisa de acompanhar. A escolha entre all, allSettled e race depende do contrato do resultado.

Criar conta

Referência: Promise.prototype.then · ECMAScript 2026 (17th edition); browser APIs identified separately