</> HTML5Advent
ENFRESDEITPT

// js · Web Platform Advent #2

Promises em JavaScript: um guia prático

Entenda as promises em JavaScript — como criá-las, then/catch/finally, encadeamento e combinar várias com Promise.all, race, any e allSettled — com exemplos executáveis e as armadilhas mais comuns.

Uma peça de quebra-cabeça vermelha encaixando no último espaço de um quebra-cabeça cinza

Uma promise é um objeto que representa um valor que ainda não está pronto. É a base do JavaScript assíncrono: fetch(), temporizadores, leituras de arquivos no Node e a maioria das APIs modernas retornam promises. Uma promise está sempre em um de três estados — pendente, resolvida ou rejeitada — e, uma vez decidida, nunca mais muda.

Criar uma promise

Na maior parte do tempo você consome promises que uma API lhe entrega. Mas você cria uma você mesmo com o construtor Promise, que recebe uma função executor que recebe resolve e reject:

const wait = (ms) => new Promise((resolve) => {
  setTimeout(resolve, ms);
});

const fetchUser = (id) => new Promise((resolve, reject) => {
  if (!id) reject(new Error('id is required'));
  else resolve({ id, name: 'Ada' });
});

Chamar resolve(value) resolve a promise com esse valor; chamar reject(error) a rejeita. Qualquer coisa lançada de forma síncrona dentro do executor também rejeita a promise.

then, catch e finally

Você lê o resultado de uma promise com .then() para o sucesso, .catch() para a falha e .finally() para a limpeza que roda de qualquer forma:

fetchUser(42)
  .then((user) => console.log('Got', user.name))
  .catch((err) => console.error('Failed:', err.message))
  .finally(() => console.log('Done'));

.then() pode receber dois argumentos — onFulfilled e onRejected — mas um .catch() separado no fim da cadeia é mais claro e também captura erros lançados dentro de manipuladores .then() anteriores.

Encadeamento: o verdadeiro superpoder

Cada .then() retorna uma nova promise, então elas se encadeiam. O que você retornar de um manipulador torna-se a entrada do próximo. Crucialmente, se você retornar uma promise, a cadeia aguarda que ela seja decidida antes de continuar:

fetch('/api/user')
  .then((res) => res.json())          // returns a promise → chain waits
  .then((user) => fetch('/api/posts?user=' + user.id))
  .then((res) => res.json())
  .then((posts) => console.log(posts.length, 'posts'))
  .catch((err) => console.error(err));

Um único .catch() no final trata uma rejeição de qualquer passo acima dele, e é por isso que o encadeamento supera o aninhamento de callbacks.

Corredores de revezamento numa pista passando o bastão de um corredor para o seguinte
Como corredores de revezamento passando um bastão, uma cadeia de promises entrega o resultado de cada passo ao próximo manipulador, em sequência.

Executar promises em conjunto

Quando as tarefas não dependem umas das outras, inicie-as em paralelo e combine os resultados. Quatro métodos estáticos cobrem os casos mais comuns:

  • Promise.all([...]) — aguarda que todas sejam resolvidas; é rejeitada assim que uma for rejeitada. Retorna um array de resultados em ordem.
  • Promise.allSettled([...]) — aguarda que todas terminem e nunca é rejeitada; retorna um array de objetos { status, value } ou { status, reason }.
  • Promise.race([...]) — é decidida com a primeira promise a ser decidida, seja ela resolvida ou rejeitada.
  • Promise.any([...]) — é decidida com a primeira promise a ser resolvida; só é rejeitada se todas forem rejeitadas.
// Fetch three resources at once, fail if any fails
const [user, posts, prefs] = await Promise.all([
  fetch('/api/user').then((r) => r.json()),
  fetch('/api/posts').then((r) => r.json()),
  fetch('/api/prefs').then((r) => r.json()),
]);

// Same, but tolerate individual failures
const results = await Promise.allSettled([taskA(), taskB(), taskC()]);
results.forEach((r) => {
  if (r.status === 'fulfilled') console.log('ok', r.value);
  else console.warn('failed', r.reason);
});

Armadilhas comuns

  • Esquecer o return. Dentro de um .then(), se você chamar uma promise mas não a retornar com return, a cadeia não vai esperá-la — você perde a ordenação e o tratamento de erros.
  • Rejeições não tratadas. Uma promise sem .catch() (ou um try/catch em torno de await) produz um unhandledrejection. Trate sempre os erros no fim da cadeia.
  • Misturar callbacks e promises. Não chame resolve duas vezes nem ambos resolve e reject — só a primeira chamada conta; as demais são ignoradas silenciosamente.
  • O executor roda imediatamente. A função que você passa para new Promise() roda de forma síncrona, na hora — apenas as callbacks de .then() são adiadas.

Referência rápida

ObjetivoMétodo
Tratar o sucesso.then(onFulfilled)
Tratar a falha.catch(onRejected)
Limpeza de qualquer forma.finally(fn)
Todas devem ter sucessoPromise.all
Coletar cada resultadoPromise.allSettled
A primeira a ser decididaPromise.race
A primeira a ter sucessoPromise.any

As promises são o encanamento por baixo de async/await, que é apenas uma sintaxe mais agradável sobre os mesmos objetos. Quando o encadeamento e os quatro combinadores se tornarem naturais, o JavaScript assíncrono deixa de ser intimidante.