Nesta aula, vamos explorar os métodos estáticos da classe Promise que permitem trabalhar com múltiplas promessas simultaneamente: Promise.all, Promise.race, Promise.allSettled e Promise.any. Esses métodos são fundamentais para lidar com operações assíncronas concorrentes, como buscar dados de várias APIs ao mesmo tempo, executar tarefas em paralelo e tratar erros de forma eficiente. Ao dominar esses utilitários, você conseguirá escrever código mais limpo, resiliente e performático.

Antes de detalharmos cada método, é importante lembrar que uma Promise representa um valor que pode estar disponível agora, no futuro ou nunca. Os métodos que veremos retornam uma nova Promise que agrega os resultados de várias outras, seguindo regras específicas de resolução e rejeição. Vamos entender cada um com profundidade.

Diferenças

Os quatro métodos diferem principalmente em como tratam a resolução e a rejeição das promessas internas. Promise.all aguarda todas as promessas serem resolvidas; se qualquer uma rejeitar, a promise resultante rejeita imediatamente com o motivo da primeira rejeição. Promise.race resolve ou rejeita com o resultado da primeira promessa que se estabelecer (resolvida ou rejeitada). Promise.allSettled aguarda todas as promessas se estabelecerem (resolvidas ou rejeitadas) e retorna um array com o status de cada uma. Promise.any resolve com a primeira promessa resolvida; se todas rejeitarem, rejeita com um AggregateError contendo todos os motivos.

Vamos resumir em uma tabela para facilitar a visualização:

MétodoComportamentoResultado
Promise.allAguarda todas as promessas serem resolvidas. Se alguma rejeitar, rejeita imediatamente.Array com os valores resolvidos na mesma ordem das promessas.
Promise.raceResolve ou rejeita com o resultado da primeira promessa que se estabelecer.O valor ou motivo da primeira promessa estabelecida.
Promise.allSettledAguarda todas as promessas se estabelecerem (resolvidas ou rejeitadas).Array de objetos com status e value ou reason.
Promise.anyResolve com a primeira promessa resolvida. Se todas rejeitarem, rejeita com AggregateError.O valor da primeira promessa resolvida.

É crucial notar que Promise.all e Promise.race são os mais antigos (ES6), enquanto Promise.allSettled e Promise.any foram adicionados em versões mais recentes do ECMAScript (ES2020 e ES2021, respectivamente). Isso significa que, em ambientes mais antigos, pode ser necessário usar polyfills ou transpiladores.

Quando usar cada um

A escolha do método certo depende do cenário. Use Promise.all quando você precisa de todos os resultados para prosseguir, e uma falha em qualquer operação deve interromper todo o fluxo. Por exemplo, ao carregar dados de várias fontes para montar uma página, se uma falhar, talvez seja melhor mostrar um erro geral em vez de uma página parcial.

Use Promise.race para cenários de competição, como implementar timeouts: você pode correr uma operação assíncrona contra um timer que rejeita após um tempo limite. Também é útil para escolher a fonte mais rápida entre várias.

Use Promise.allSettled quando você precisa aguardar todas as operações terminarem, independentemente de sucesso ou falha, e quer tratar cada resultado individualmente. Um exemplo clássico é executar uma série de tarefas de limpeza ou logging, onde cada uma pode falhar, mas você não quer abortar as demais.

Use Promise.any quando você quer o primeiro sucesso entre várias operações, mas não se importa se algumas falharem. Por exemplo, tentar conectar a vários servidores e usar o primeiro que responder com sucesso.

Exemplos práticos

Vamos criar funções auxiliares para simular operações assíncronas com atrasos e possíveis rejeições. Usaremos setTimeout para simular latência e Math.random para decidir se a promessa resolve ou rejeita.

function simulateAsync(id, delay, shouldReject = false) {
  return new Promise((resolve, reject) => {
    setTimeout(() => {
      if (shouldReject) {
        reject(new Error(`Falha na operação ${id}`));
      } else {
        resolve(`Resultado da operação ${id}`);
      }
    }, delay);
  });
}

// Exemplo com Promise.all
const promessas = [
  simulateAsync(1, 1000),
  simulateAsync(2, 2000),
  simulateAsync(3, 1500)
];

Promise.all(promessas)
  .then(resultados => console.log('Promise.all:', resultados))
  .catch(erro => console.error('Promise.all rejeitou:', erro.message));

No exemplo acima, se todas as promessas resolverem, teremos um array com os três resultados. Se alguma rejeitar, o .catch capturará o erro, e os outros resultados serão ignorados.

Agora um exemplo de Promise.race com timeout:

function withTimeout(promise, ms) {
  const timeout = new Promise((_, reject) => {
    setTimeout(() => reject(new Error('Tempo esgotado')), ms);
  });
  return Promise.race([promise, timeout]);
}

const operacaoLenta = simulateAsync('lenta', 3000);
withTimeout(operacaoLenta, 2000)
  .then(resultado => console.log('Race:', resultado))
  .catch(erro => console.error('Race:', erro.message));

Aqui, a operação lenta é corrida contra um timer de 2 segundos. O timer rejeita primeiro, então o .catch recebe o erro de timeout.

Para Promise.allSettled, vamos simular várias operações com algumas falhas:

const mistas = [
  simulateAsync('A', 500),
  simulateAsync('B', 1000, true), // rejeita
  simulateAsync('C', 800)
];

Promise.allSettled(mistas)
  .then(resultados => {
    resultados.forEach((resultado, indice) => {
      if (resultado.status === 'fulfilled') {
        console.log(`allSettled: Operação ${indice+1} resolveu com ${resultado.value}`);
      } else {
        console.error(`allSettled: Operação ${indice+1} rejeitou com ${resultado.reason.message}`);
      }
    });
  });

O array de resultados contém objetos com status e value (se resolvida) ou reason (se rejeitada). Isso permite tratar cada caso individualmente.

Por fim, Promise.any:

const servidores = [
  simulateAsync('Servidor 1', 3000, true), // falha
  simulateAsync('Servidor 2', 1000),
  simulateAsync('Servidor 3', 2000, true) // falha
];

Promise.any(servidores)
  .then(resultado => console.log('any:', resultado))
  .catch(erro => console.error('any: todas falharam', erro.errors));

Aqui, o Servidor 2 resolve primeiro, então Promise.any resolve com seu valor, ignorando as falhas dos outros. Se todos rejeitassem, o catch receberia um AggregateError com a propriedade errors contendo todos os motivos.

Boas práticas

Ao usar esses métodos, lembre-se de sempre tratar os erros com .catch ou try/catch em funções assíncronas. Para Promise.all, evite que uma falha em uma operação deixe as outras pendentes; considere usar Promise.allSettled se você precisar que todas completem. Utilize Promise.race para timeouts, mas cuidado com promessas que ficam pendentes (não resolvem nem rejeitam) — isso pode causar vazamento de memória se não forem canceladas. Prefira Promise.any para casos de failover.

Outra boa prática é usar Promise.all com Promise.allSettled combinados para obter o melhor dos dois mundos: aguardar todas, mas tratar falhas individualmente. Por exemplo, você pode usar Promise.allSettled e depois filtrar os resultados resolvidos.

Exercícios

  1. Escreva uma função obterTodosComFalha que recebe um array de promessas e retorna uma promessa que resolve com um array de valores, mas se qualquer promessa rejeitar, rejeita imediatamente com o motivo da primeira rejeição. Use Promise.all.
  2. Implemente um timeout genérico usando Promise.race que rejeita após um número especificado de milissegundos, como no exemplo.
  3. Crie uma função aguardarTodosEstabelecidos que recebe um array de promessas e retorna um array de objetos com status e value ou reason. Use Promise.allSettled.
  4. Crie uma função primeiroSucesso que recebe um array de promessas e retorna a primeira promessa resolvida. Se todas rejeitarem, retorne um erro com a mensagem "Todas as promessas falharam". Use Promise.any.
  5. Combine Promise.allSettled e Promise.all para criar uma função obterValoresApenasResolvidos que recebe um array de promessas e retorna um array com os valores das promessas que foram resolvidas, ignorando as rejeitadas.

✓ Resposta:
function obterTodosComFalha(promessas) {
  return Promise.all(promessas);
}
// Uso:
// obterTodosComFalha([Promise.resolve(1), Promise.reject(new Error('erro'))])
//   .then(console.log)
//   .catch(erro => console.error(erro.message)); // "erro"

✓ Resposta:
function withTimeout(promise, ms) {
  const timeout = new Promise((_, reject) => {
    setTimeout(() => reject(new Error('Tempo esgotado')), ms);
  });
  return Promise.race([promise, timeout]);
}

✓ Resposta:
function aguardarTodosEstabelecidos(promessas) {
  return Promise.allSettled(promessas);
}

✓ Resposta:
function primeiroSucesso(promessas) {
  return Promise.any(promessas).catch(() => {
    throw new Error('Todas as promessas falharam');
  });
}

✓ Resposta:
async function obterValoresApenasResolvidos(promessas) {
  const resultados = await Promise.allSettled(promessas);
  return resultados
    .filter(r => r.status === 'fulfilled')
    .map(r => r.value);
}

Referências