Pular para o conteúdo
Cursos

DevClub

LógicaFront-endBack-endMobile

IA Club

IA na prática
Estudar programaçãoEstudar IA
LiçãoIntermediáriocódigo testado

Promise.all, allSettled e race em JavaScript

Quatro formas de combinar Promises e o que muda entre elas: fail-fast, relatório completo, primeira resposta — com o tempo medido em requisições de verdade.

Rodolfo Mori5 min de leitura

Promise.all recebe uma lista de Promises e devolve uma Promise só, que resolve com o array de todos os resultados. É a ferramenta para disparar várias operações independentes ao mesmo tempo e esperar uma vez em vez de várias.

Ao lado dela existem outras três, e a escolha entre elas é sempre a mesma pergunta: o que deve acontecer quando uma das operações falha? all desiste de tudo, allSettled entrega o relatório completo, race pega a primeira que terminar e any pega a primeira que der certo.

Os exemplos rodam contra a API local da loja apresentada em como consumir uma API com fetch.

Uma comanda com vários pedidos independentes

Imagine uma mesa que pediu café, suco e pão na chapa. Se a cozinha esperar o café ficar pronto para começar o suco, a mesa paga pela soma de todos os tempos. Quando os três pedidos são independentes, eles podem começar juntos; a comanda só é entregue quando a regra escolhida disser que terminou.

Promise.all é o combinador que espera todas e falha na primeira rejeição. allSettled, race e any mantêm o início paralelo, mas mudam a regra de encerramento. Na primeira medição, some os atrasos individuais e depois compare com o tempo total real: a diferença verifica que as operações foram disparadas em paralelo, não uma depois da outra.

Três chamadas independentes, com 400ms, 300ms e 500ms de resposta. Uma de cada vez:

js
const inicio = Date.now();

const produtos = await fetch('http://localhost:4599/produtos?ms=400').then((r) => r.json());
const estoque = await fetch('http://localhost:4599/estoque?id=1&ms=300').then((r) => r.json());
const frete = await fetch('http://localhost:4599/frete?cep=01310100&ms=500').then((r) => r.json());

console.log(produtos.length, estoque.disponivel, frete.valor);
console.log('em série:', Date.now() - inicio, 'ms');
3 true 24.9 em série: 1236 ms

Agora as mesmas três, disparadas juntas:

js
const inicio = Date.now();

const [produtos, estoque, frete] = await Promise.all([
  fetch('http://localhost:4599/produtos?ms=400').then((r) => r.json()),
  fetch('http://localhost:4599/estoque?id=1&ms=300').then((r) => r.json()),
  fetch('http://localhost:4599/frete?cep=01310100&ms=500').then((r) => r.json()),
]);

console.log(produtos.length, estoque.disponivel, frete.valor);
console.log('em paralelo:', Date.now() - inicio, 'ms');
3 true 24.9 em paralelo: 523 ms

1236ms contra 523ms. O tempo em série é a soma; em paralelo é o maior, mais o custo de abrir as conexões. Numa tela que carrega seis blocos de dados, essa diferença é a distância entre um carregamento instantâneo e um usuário olhando para um spinner.

A ordem do array é a de entrada, não a de chegada

Isso é garantido pela especificação e é o que permite desestruturar o resultado com segurança:

js
const espera = (ms, valor) =>
  new Promise((resolve) =>
    setTimeout(() => {
      console.log(`terminou: ${valor} (${ms}ms)`);
      resolve(valor);
    }, ms),
  );

const resultados = await Promise.all([
  espera(500, 'frete'),
  espera(100, 'estoque'),
  espera(300, 'cupom'),
]);

console.log('array final:', resultados);
terminou: estoque (100ms) terminou: cupom (300ms) terminou: frete (500ms) array final: [ 'frete', 'estoque', 'cupom' ]

Chegaram em uma ordem, saíram na outra. É por isso que const [cliente, frete] = await Promise.all([...]) nunca troca as variáveis de lugar, por mais instável que seja a rede.

all é fail-fast — e não cancela nada

Uma rejeição derruba a chamada inteira, na hora. Mas as outras Promises não param: elas continuam rodando, invisíveis, e terminam depois.

js
const espera = (ms, valor) =>
  new Promise((resolve) =>
    setTimeout(() => {
      console.log(`  terminou mesmo assim: ${valor}`);
      resolve(valor);
    }, ms),
  );

const falhar = (ms, motivo) =>
  new Promise((_, reject) => setTimeout(() => reject(new Error(motivo)), ms));

const inicio = Date.now();

try {
  await Promise.all([
    espera(500, 'frete'),
    falhar(100, 'transportadora fora do ar'),
    espera(300, 'cupom'),
  ]);
} catch (e) {
  console.log(`Promise.all rejeitou aos ${Date.now() - inicio}ms:`, e.message);
}

await espera(600, 'fim da observação');
Promise.all rejeitou aos 102ms: transportadora fora do ar terminou mesmo assim: cupom terminou mesmo assim: frete terminou mesmo assim: fim da observação

Duas leituras. Primeira: o catch recebeu um erro — o primeiro. Se três falharem, você fica sabendo de uma. Segunda: cupom e frete terminaram depois da rejeição, gastando conexão e banda de quem já desistiu. Para interromper de verdade é preciso um AbortController compartilhado entre as requisições.

allSettled: o relatório de todas

Nunca rejeita. Devolve um array de objetos com status, e value ou reason. Num arquivo novo, com as duas ajudantes silenciosas — sem o console.log que a seção anterior usou para espiar o fail-fast:

js
const espera = (ms, valor) => new Promise((resolve) => setTimeout(() => resolve(valor), ms));
const falhar = (ms, motivo) =>
  new Promise((_, reject) => setTimeout(() => reject(new Error(motivo)), ms));

const resultados = await Promise.allSettled([
  espera(500, { servico: 'frete', valor: 24.9 }),
  falhar(100, 'transportadora fora do ar'),
  espera(300, { servico: 'cupom', desconto: 10 }),
]);

console.log(resultados);

const oks = resultados.filter((r) => r.status === 'fulfilled').map((r) => r.value);
const falhas = resultados.filter((r) => r.status === 'rejected').map((r) => r.reason.message);

console.log('deu certo:', oks.length, '| falhou:', falhas);
[ { status: 'fulfilled', value: { servico: 'frete', valor: 24.9 } }, { status: 'rejected', reason: Error: transportadora fora do ar at Timeout._onTimeout (file:///private/tmp/loja-async/allsettled.mjs:3:54) at listOnTimeout (node:internal/timers:605:17) at process.processTimers (node:internal/timers:541:7) }, { status: 'fulfilled', value: { servico: 'cupom', desconto: 10 } } ] deu certo: 2 | falhou: [ 'transportadora fora do ar' ]

O preço é ter que abrir cada item para saber o que veio — não dá para desestruturar direto. Em troca, você recebe o motivo de cada falha, e não só o primeiro.

race e any: quem chegar primeiro

race entrega o primeiro resultado, seja ele qual for — inclusive uma rejeição:

js
const vencedor = await Promise.race([espera(500, 'transportadora A'), espera(200, 'transportadora B')]);
console.log('cotação mais rápida:', vencedor);

try {
  await Promise.race([espera(500, 'transportadora A'), falhar(100, 'transportadora B caiu')]);
} catch (e) {
  console.log('race também perde para uma rejeição:', e.message);
}
cotação mais rápida: transportadora B race também perde para uma rejeição: transportadora B caiu

any ignora as rejeições e espera a primeira que der certo. Se todas falharem, ele rejeita com um AggregateError que carrega a lista de motivos:

js
const primeira = await Promise.any([
  falhar(100, 'transportadora A caiu'),
  espera(300, 'transportadora B: R$ 24,90'),
  espera(500, 'transportadora C: R$ 19,90'),
]);
console.log('primeira que respondeu:', primeira);

try {
  await Promise.any([falhar(100, 'A caiu'), falhar(200, 'B caiu')]);
} catch (e) {
  console.log('nome:', e.name);
  console.log('mensagem:', e.message);
  console.log('motivos:', e.errors.map((x) => x.message));
}
primeira que respondeu: transportadora B: R$ 24,90 nome: AggregateError mensagem: All promises were rejected motivos: [ 'A caiu', 'B caiu' ]

O e.errors é exclusivo do AggregateError e é o que salva o diagnóstico: sem ele, “todas falharam” não diz por quê. Um uso honesto de race é impor tempo-limite a uma operação que não tem cancelamento próprio; para requisições HTTP, prefira AbortSignal.timeout.

Dez requisições, uma falha

O cenário que aparece em produção: uma listagem carrega dez produtos e um dos ids não existe mais.

js
const ids = [1, 2, 3, 99, 1, 2, 3, 1, 2, 3];

const buscar = async (id) => {
  const r = await fetch(`http://localhost:4599/produtos/${id}`);
  if (!r.ok) throw new Error(`produto ${id}: HTTP ${r.status}`);
  return r.json();
};

const produtos = await Promise.all(ids.map(buscar));
console.log('carregou', produtos.length, 'produtos');
file:///private/tmp/loja-async/all-dez.mjs:5 if (!r.ok) throw new Error(`produto ${id}: HTTP ${r.status}`); ^ Error: produto 99: HTTP 404 at buscar (file:///private/tmp/loja-async/all-dez.mjs:5:20) at process.processTicksAndRejections (node:internal/process/task_queues:104:5) at async Promise.all (index 3) at async file:///private/tmp/loja-async/all-dez.mjs:9:18 Node.js v24.16.0

Nove produtos vieram e nenhum foi aproveitado: a tela ficou vazia por causa de um. E repare no presente que o stack trace dá — at async Promise.all (index 3) diz exatamente qual posição do array rejeitou. É a informação que economiza meia hora de log.

Trocando uma palavra, a listagem passa a mostrar o que deu certo:

js
const resultados = await Promise.allSettled(ids.map(buscar));

const carregados = resultados.filter((r) => r.status === 'fulfilled');
const quebrados = resultados.filter((r) => r.status === 'rejected');

console.log('carregou', carregados.length, 'de', ids.length);
for (const r of quebrados) console.log('  falhou:', r.reason.message);
carregou 9 de 10 falhou: produto 99: HTTP 404

Qual usar em cada caso

método resolve quando rejeita quando resultado
all todas resolvem a primeira rejeitar array de valores, na ordem de entrada
allSettled todas terminam nunca array de {status, value/reason}
race a primeira terminar a primeira terminar for rejeição o valor ou o erro de quem chegou antes
any a primeira resolver todas rejeitarem o valor, ou AggregateError com errors

Um último cuidado que a tabela não mostra: Promise.all não tem limite de concorrência. Mil ids em map viram mil requisições simultâneas, e o servidor provavelmente vai responder com 429 ou simplesmente derrubar as conexões. Para lotes grandes, fatie o array em blocos de dez ou vinte e chame all por bloco — ou use uma biblioteca de fila com limite.

Com isso fecha o bloco assíncrono da trilha de JavaScript: event loop, timers, Promise, async/await, fetch e combinadores. O guia de JavaScript mostra o que vem depois — e, se o próximo projeto envolve chamar uma API de modelo de linguagem, os combinadores desta lição são exatamente o que segura várias chamadas caras em paralelo; o contexto está em o que é um LLM.

  • promise all
  • allsettled
  • race
  • promise any
  • assincrono

Perguntas frequentes

Promise.all cancela as outras requisições quando uma falha?
Não cancela nada. A Promise devolvida por all rejeita imediatamente, mas as outras continuam rodando até o fim, invisíveis. Para realmente interromper, é preciso um AbortController compartilhado pelas requisições.
A ordem do array de resultados é a ordem de chegada?
Não. É a ordem do array de entrada, sempre, independentemente de quem terminou primeiro. Isso é o que permite desestruturar o resultado em variáveis nomeadas com segurança.
Quando usar allSettled em vez de all?
Quando um pedaço da tela pode faltar sem inviabilizar o resto — cotações de frete de várias transportadoras, por exemplo. Se qualquer falha invalida a operação inteira, all é o certo, porque falha cedo.
Qual a diferença entre race e any?
race entrega o primeiro resultado, seja ele sucesso ou falha. any ignora as rejeições e entrega a primeira que der certo; só rejeita se todas falharem, e aí com um AggregateError contendo todos os motivos.

Dúvidas e comentários

Travou em algum passo? Pergunte aqui — a equipe e outros alunos respondem.

Todo o código deste artigo foi executado em Node 24.16.0, e as saídas exibidas são as reais — como produzimos este conteúdo.

Fontes consultadas

  1. MDN — Promise.all() — developer.mozilla.org
  2. MDN — Promise.allSettled() — developer.mozilla.org

Continue por aqui