As Promises são fundamentais no JavaScript moderno para lidar com operações assíncronas. Quando você precisa coordenar múltiplas operações assíncronas, os métodos estáticos Promise.all, Promise.race, Promise.allSettled e Promise.any tornam-se ferramentas indispensáveis. Cada um tem um comportamento distinto em relação a como trata sucessos e falhas, e entender essas nuances é crucial para escrever código assíncrono robusto e previsível.

Nesta aula, vamos mergulhar em cada um desses métodos, comparando suas características e aplicações. Você verá como eles podem simplificar o gerenciamento de múltiplas Promises e evitar armadilhas comuns. Ao final, você terá um entendimento claro de qual método usar em diferentes situações, munido de exemplos práticos que consolidam o aprendizado.

Diferenças

Os quatro métodos estáticos de Promise — all, race, allSettled e any — operam sobre um iterável de Promises (geralmente um array) e retornam uma nova Promise. A principal diferença está em como eles lidam com a resolução (fulfillment) e rejeição (rejection) das Promises internas.

Promise.all aguarda todas as Promises serem resolvidas. Se qualquer uma rejeitar, a Promise resultante é rejeitada imediatamente com o motivo da primeira rejeição, e as demais são ignoradas (embora continuem executando). Isso é útil quando você precisa de todos os resultados para prosseguir — por exemplo, buscar dados de várias APIs e combinar os resultados.

Promise.race resolve ou rejeita assim que a primeira Promise do iterável se resolve ou rejeita. É como uma corrida: o primeiro a terminar determina o resultado. Esse método é útil para implementar timeouts ou quando você quer o resultado mais rápido entre várias operações concorrentes.

Promise.allSettled aguarda todas as Promises serem concluídas (resolvidas ou rejeitadas) e retorna um array de objetos que descrevem o resultado de cada uma, com status "fulfilled" ou "rejected". Não falha prematuramente; ele sempre espera todas as Promises, permitindo que você trate cada resultado individualmente.

Promise.any é o oposto de Promise.all em termos de sucesso: ele resolve com o valor da primeira Promise que for resolvida, ignorando rejeições. Se todas as Promises forem rejeitadas, ele rejeita com um AggregateError contendo todos os motivos. É útil quando você quer o primeiro sucesso entre várias alternativas, como tentar vários servidores e usar o primeiro que responder.

A tabela a seguir resume as diferenças:

MétodoResolve quandoRejeita quando
Promise.allTodas as Promises resolvemQualquer Promise rejeita
Promise.raceA primeira Promise resolve ou rejeitaA primeira Promise rejeita
Promise.allSettledTodas as Promises concluem (resolvidas ou rejeitadas)Nunca rejeita (sempre resolve)
Promise.anyA primeira Promise resolveTodas as Promises rejeitam

Quando usar cada um

A escolha do método certo depende do seu objetivo e do comportamento desejado em caso de falhas. Vamos analisar cenários típicos.

Promise.all é ideal quando você precisa de todos os resultados para continuar. Por exemplo, ao carregar dados de múltiplos endpoints e só então montar a página, ou ao executar várias tarefas que são pré-requisitos para uma ação seguinte. Se uma falha é crítica e impede a operação completa, use Promise.all.

Promise.race é útil para cenários de competição, como implementar um timeout: você pode correr uma operação contra um timer que rejeita após um certo tempo. Também é usado para escolher a fonte mais rápida de dados, como em um sistema de cache em que você consulta várias fontes e usa a primeira resposta.

Promise.allSettled é perfeito quando você quer executar várias Promises independentes e precisa saber o resultado de cada uma, mesmo que algumas falhem. Por exemplo, ao enviar notificações para vários usuários: você quer saber quais foram enviadas e quais falharam, sem que uma falha interrompa o processamento das demais.

Promise.any é usado quando você quer o primeiro sucesso entre várias alternativas, como tentar conectar a vários servidores de réplica e usar o primeiro que responder. Também é útil para fallbacks: se uma operação falhar, tente outra até que uma tenha sucesso.

Exemplos práticos

Vamos colocar a teoria em prática com exemplos que você pode executar no seu ambiente Node.js ou no console do navegador. Usaremos funções que retornam Promises com atrasos simulados para demonstrar o comportamento.

Exemplo com Promise.all

const p1 = Promise.resolve(10);
const p2 = new Promise((resolve, reject) => setTimeout(() => resolve(20), 1000));
const p3 = Promise.resolve(30);

Promise.all([p1, p2, p3])
  .then(values => console.log('Resultado:', values)) // [10, 20, 30]
  .catch(err => console.error('Erro:', err));

Neste exemplo, todas as Promises resolvem, então Promise.all resolve com um array contendo os valores na mesma ordem. Se qualquer uma rejeitasse, o .catch seria acionado imediatamente.

Exemplo com Promise.race

const timeout = new Promise((resolve, reject) => setTimeout(() => reject('Timeout!'), 2000));
const operation = new Promise((resolve, reject) => setTimeout(() => resolve('Operação concluída'), 3000));

Promise.race([operation, timeout])
  .then(result => console.log('Vencedor:', result))
  .catch(err => console.error('Erro:', err)); // Timeout!

Aqui, a Promise de timeout rejeita após 2 segundos, antes da operação (3 segundos). Portanto, Promise.race rejeita com 'Timeout!'. Se a operação fosse mais rápida, ela venceria.

Exemplo com Promise.allSettled

const promises = [
  Promise.resolve('Sucesso 1'),
  Promise.reject('Falha 2'),
  Promise.resolve('Sucesso 3')
];

Promise.allSettled(promises)
  .then(results => {
    results.forEach((result, index) => {
      if (result.status === 'fulfilled') {
        console.log(`Promise ${index + 1}: ${result.value}`);
      } else {
        console.log(`Promise ${index + 1}: ${result.reason}`);
      }
    });
  });

O resultado será um array com objetos {status: 'fulfilled', value: ...} ou {status: 'rejected', reason: ...}. Isso permite tratar cada Promise individualmente.

Exemplo com Promise.any

const p1 = new Promise((resolve, reject) => setTimeout(() => reject('Falha 1'), 500));
const p2 = new Promise((resolve, reject) => setTimeout(() => resolve('Sucesso 2'), 1000));
const p3 = new Promise((resolve, reject) => setTimeout(() => reject('Falha 3'), 1500));

Promise.any([p1, p2, p3])
  .then(result => console.log('Resultado:', result)) // Sucesso 2
  .catch(err => console.error('Erro:', err));

Aqui, p1 rejeita primeiro, mas Promise.any ignora e espera a primeira resolução, que é p2. Se todas rejeitassem, o erro seria um AggregateError.

Boas práticas e observações

Ao usar esses métodos, lembre-se de que as Promises dentro do iterável são executadas em paralelo, mas a ordem de resolução não é garantida. Sempre trate os erros adequadamente com .catch() ou try/catch em funções async.

Uma armadilha comum é esquecer que Promise.all rejeita imediatamente, mas as outras Promises continuam executando. Isso pode causar efeitos colaterais indesejados. Se você não quer que isso aconteça, use Promise.allSettled.

Outra dica: para Promise.any, o erro AggregateError tem uma propriedade errors que contém todos os motivos. Acesse-a para depuração.

Finalmente, esses métodos estão disponíveis em todos os ambientes modernos (Node.js 12.9+ para Promise.any). Certifique-se de verificar a compatibilidade se estiver trabalhando com ambientes mais antigos.

Referências

Exercícios

  1. Crie uma função simulateRequest que retorna uma Promise que resolve após um atraso aleatório (entre 1 e 3 segundos) com um número aleatório entre 1 e 100. Use Promise.all para executar 3 dessas funções e exibir a soma dos resultados.

    ✓ Resposta:
    function simulateRequest() {
      const delay = Math.random() * 2000 + 1000;
      return new Promise((resolve) => {
        setTimeout(() => resolve(Math.floor(Math.random() * 100) + 1), delay);
      });
    }
    
    Promise.all([simulateRequest(), simulateRequest(), simulateRequest()])
      .then(values => {
        const sum = values.reduce((acc, val) => acc + val, 0);
        console.log('Soma:', sum);
      });
  2. Escreva uma função que retorne uma Promise que resolve após 2 segundos com a string "Tarefa concluída". Crie outra Promise que rejeita após 1 segundo com "Erro rápido". Use Promise.race para ver qual vence e registre o resultado.
  3. ✓ Resposta:
    const task = new Promise(resolve => setTimeout(() => resolve('Tarefa concluída'), 2000));
    const error = new Promise((resolve, reject) => setTimeout(() => reject('Erro rápido'), 1000));
    
    Promise.race([task, error])
      .then(result => console.log('Resultado:', result))
      .catch(err => console.error('Erro:', err)); // Erro rápido
  4. Dado um array de Promises onde algumas rejeitam, use Promise.allSettled para imprimir uma mensagem para cada Promise indicando se foi resolvida ou rejeitada.
  5. ✓ Resposta:
    const promises = [
      Promise.resolve('Sucesso'),
      Promise.reject('Falha'),
      Promise.resolve('Outro sucesso')
    ];
    
    Promise.allSettled(promises)
      .then(results => {
        results.forEach((result, index) => {
          if (result.status === 'fulfilled') {
            console.log(`Promessa ${index + 1} resolvida com: ${result.value}`);
          } else {
            console.log(`Promessa ${index + 1} rejeitada com: ${result.reason}`);
          }
        });
      });
  6. Crie um array de três Promises: as duas primeiras rejeitam com mensagens diferentes, e a terceira resolve com "Sucesso". Use Promise.any para obter o valor da primeira resolução e imprima-o.
  7. ✓ Resposta:
    const p1 = Promise.reject('Falha 1');
    const p2 = Promise.reject('Falha 2');
    const p3 = Promise.resolve('Sucesso');
    
    Promise.any([p1, p2, p3])
      .then(value => console.log('Resultado:', value)) // Sucesso
      .catch(err => console.error('Erro:', err));
  8. Explique com suas palavras a diferença entre Promise.all e Promise.allSettled e dê um exemplo de cenário onde cada um seria mais adequado.
  9. ✓ Resposta:

    Promise.all falha rápido: se qualquer Promise rejeitar, a Promise resultante é rejeitada imediatamente, e os outros resultados são ignorados. Promise.allSettled espera todas as Promises e retorna o status de cada uma, nunca rejeitando. Use Promise.all quando precisar de todos os dados para prosseguir e uma falha seja crítica, como carregar dados de múltiplas APIs para montar uma página. Use Promise.allSettled quando quiser executar tarefas independentes e precisar saber o resultado individual, como enviar notificações para vários usuários, onde uma falha não deve impedir as outras.