Menu

Promises em JavaScript: then, catch e Promise.all

Entenda como funcionam as Promises em JavaScript: os três estados, encadeamento com then e catch, execução paralela com Promise.all e como criar a sua com new Promise.

Promise em JavaScript: um espaço reservado para um valor futuro

Quando o JavaScript precisa executar alguma tarefa que leva tempo — uma requisição de rede, a leitura de um arquivo, a espera por um timer — ele não tem como devolver o resultado na hora. No lugar disso, você recebe uma Promise: um objeto que representa um valor que vai existir em algum momento.

index.js
Output
Click Run to see the output here.

O primeiro console.log mostra uma Promise no estado pendente. Meio segundo depois, a Promise é resolvida e o callback do .then roda com o valor. A Promise em si é só um objeto; o pulo do gato é que ela sabe avisar quem estiver escutando assim que o valor chega.

Os três estados de uma Promise

Toda Promise está sempre em um destes três estados:

  • pending (pendente) — o trabalho ainda está em andamento. Nenhum valor disponível.
  • fulfilled (resolvida) — o trabalho deu certo. Tem um valor pronto.
  • rejected (rejeitada) — o trabalho falhou. Tem um erro disponível.

Uma Promise transita de pending para fulfilled ou rejected uma única vez — e fica nesse estado para sempre. Não tem como "desresolver" uma Promise, nem resolvê-la duas vezes.

index.js
Output
Click Run to see the output here.

Promise.resolve(valor) cria uma Promise que já nasce resolvida; Promise.reject(erro) cria uma que já nasce rejeitada. É útil em testes e quando você precisa retornar uma Promise de uma função que, em alguns casos, já tem a resposta na hora.

Lendo o valor: .then e .catch

Você não acessa o valor de uma Promise diretamente — em vez disso, passa um callback para o .then, e a Promise chama esse callback assim que o valor fica pronto:

index.js
Output
Click Run to see the output here.

.catch(fn) é executado quando a Promise é rejeitada. Por baixo dos panos, é só um atalho para .then(undefined, fn). Um .catch() no final da cadeia lida com rejeições de qualquer etapa anterior — não precisa colocar um depois de cada .then.

Encadeamento de promises: cada .then devolve uma nova Promise

É aqui que muita gente tropeça. O .then() não apenas executa um callback — ele retorna uma nova Promise, que resolve com o valor que o callback devolveu. É isso que permite o encadeamento:

index.js
Output
Click Run to see the output here.

Cada etapa alimenta a próxima. Se um callback .then retornar uma Promise, a cadeia espera essa Promise ser resolvida antes de seguir em frente — é assim que dá pra compor passos assíncronos de forma limpa:

index.js
Output
Click Run to see the output here.

Três etapas assíncronas em sequência, sem aninhamento. Compare isso com a mesma lógica escrita com callbacks e você vai entender por que as Promises pegaram tão bem.

Erros se propagam pela cadeia

Uma Promise rejeitada pula todos os .then até encontrar um .catch. Esse é o modelo de tratamento de erros por inteiro:

index.js
Output
Click Run to see the output here.

Lançar uma exceção dentro de um .then rejeita a Promise que aquele .then retornou. O próximo .then percebe a rejeição e vai repassando adiante, até que algum .catch a absorva. Geralmente, basta um único .catch no final da cadeia — e uma cadeia sem nenhum .catch vai disparar aquele aviso de "unhandled promise rejection", que é algo que você quer corrigir.

Criando sua própria Promise com new Promise

Na maioria das vezes, você vai só consumir Promises que as bibliotecas já entregam prontas. Mas, de vez em quando, é preciso embrulhar algo que não retorna uma — normalmente alguma API antiga baseada em callbacks:

index.js
Output
Click Run to see the output here.

A função que você passa para new Promise é chamada de executor. Ela recebe dois argumentos: resolve (chame com o valor de sucesso) e reject (chame com um erro). Você deve chamar um deles exatamente uma vez. Depois disso, chamadas adicionais são ignoradas.

Dois hábitos que evitam dor de cabeça:

  • Só use new Promise quando estiver envelopando algo que ainda não é baseado em Promise. Se a função já retorna uma Promise, apenas retorne ela.
  • Sempre faça reject com um objeto Error, nunca com uma string. Vale a pena preservar o stack trace.

Executando tarefas em paralelo com Promise.all

Encadeamentos com .then rodam em sequência. Quando você tem várias tarefas assíncronas independentes e quer que elas rodem ao mesmo tempo, o Promise.all é a ferramenta certa:

index.js
Output
Click Run to see the output here.

Os três timers rodam em paralelo. O Promise.all resolve com um array de resultados na mesma ordem da entrada — assim que todas as Promises forem cumpridas. O tempo total fica em torno de 400ms, e não 900ms.

Tem um porém: o Promise.all rejeita assim que qualquer uma das Promises for rejeitada, e os demais resultados se perdem. Esse é o comportamento certo quando você precisa de todas as partes (por exemplo, ao renderizar uma página que depende de três chamadas de API). Quando não é o caso, use o allSettled.

Quando algumas falhas são aceitáveis: Promise.allSettled

O Promise.allSettled espera todas as Promises terminarem — seja cumprida ou rejeitada — e devolve um relatório:

index.js
Output
Click Run to see the output here.

Cada resultado vem como um objeto: { status: "fulfilled", value } ou { status: "rejected", reason }. É ideal quando dá pra conviver com sucesso parcial — tipo registrar um lote de eventos, buscar várias miniaturas ou rodar checagens de saúde independentes.

Tem mais dois combinadores que vale a pena conhecer:

  • Promise.race([...]) — resolve assim que a primeira Promise se acomoda, seja com sucesso ou erro. Ótimo pra implementar timeouts.
  • Promise.any([...]) — resolve com o primeiro sucesso e ignora as rejeições. Só rejeita se todas as Promises falharem.

Promises são sempre assíncronas

Mesmo uma Promise já resolvida chama o callback do .then de forma assíncrona — nunca de forma síncrona, nunca no mesmo tick:

index.js
Output
Click Run to see the output here.

A saída é antes, depois, imediato. O callback do .then espera o código atual terminar e só então roda na fila de microtasks. Essa regra — "um callback de Promise nunca executa de forma síncrona" — é o que torna previsível a mistura de Promises com código síncrono: o código síncrono sempre termina primeiro.

Próximo passo: async/await

Encadear chamadas .then funciona bem, mas quando você passa de duas ou três etapas o código começa a parecer uma escadinha. O async/await é uma sintaxe construída em cima das Promises que permite escrever a mesma lógica como se fosse síncrona — com try/catch para tratar erros e variáveis comuns para guardar valores intermediários. É o que vamos ver a seguir.

Perguntas frequentes

O que é uma Promise em JavaScript?

Uma Promise é um objeto que representa um valor que ainda não está disponível — normalmente o resultado futuro de uma operação assíncrona, como uma requisição à rede. Ela sempre está em um de três estados: pending, fulfilled ou rejected. Para ler o valor quando ele ficar pronto, você registra callbacks com .then() e .catch().

Qual a diferença entre then e catch?

O .then(onFulfilled) é chamado quando a Promise resolve com sucesso e recebe o valor resolvido. Já o .catch(onRejected) roda quando a Promise (ou qualquer outra anterior na cadeia) é rejeitada, recebendo o erro. Um único .catch() no final da cadeia dá conta de qualquer falha que aconteça em qualquer etapa acima.

Para que serve o Promise.all?

O Promise.all([p1, p2, p3]) recebe um array de Promises e devolve uma única Promise que resolve com um array contendo todos os valores — mas só depois que todas as Promises do array tiverem resolvido. Se alguma delas for rejeitada, o Promise.all inteiro é rejeitado na hora. Quando você quer todos os resultados mesmo com falhas pelo caminho, use Promise.allSettled.

É melhor usar Promises ou async/await?

É a mesma coisa por baixo dos panos — async/await é só uma sintaxe mais enxuta em cima das Promises. Código novo geralmente fica mais legível com async/await, mas você continua retornando Promises, continua tratando erros com try/catch (ou .catch()) e ainda usa Promise.all quando quer rodar coisas em paralelo. Entender Promises é o que faz o async/await fazer sentido.

Aprenda a programar com o Coddy

COMEÇAR