Javascript

Promises: resolvendo o Callback Hell Já leu

10 min de leitura

Promises: resolvendo o Callback Hell
No artigo anterior vimos o Callback Hell — código que cresce horizontalmente, difícil de ler e manter. As Promises foram introduzidas n

No artigo anterior vimos o Callback Hell — código que cresce horizontalmente, difícil de ler e manter. As Promises foram introduzidas no ES6 (2015) como a solução oficial do JavaScript para esse problema.

Uma Promise representa um valor que ainda não está disponível, mas que estará em algum momento no futuro — ou que pode falhar. Em vez de passar um callback para dentro de uma função, você recebe um objeto que representa a operação em andamento e encadeia o que fazer com o resultado.


O conceito

Pense em uma Promise como um recibo de pedido num restaurante. Você faz o pedido (inicia a operação), recebe um número (a Promise), e continua fazendo outras coisas. Quando o pedido fica pronto, o garçom te avisa (resolve). Se algo deu errado na cozinha, você também é avisado (reject).

// Uma Promise tem sempre um de três estados:
// pending   → aguardando (operação em andamento)
// fulfilled → concluída com sucesso (resolve foi chamado)
// rejected  → falhou (reject foi chamado)

Criando uma Promise

const minhaPromise = new Promise(function(resolve, reject) {
  // O executor — executa imediatamente

  const sucesso = true;

  if (sucesso) {
    resolve("Operação concluída com sucesso!"); // fulfills a promise
  } else {
    reject(new Error("Algo deu errado.")); // rejects a promise
  }
});

O construtor recebe uma função chamada executor, que recebe dois callbacks:

  • resolve(valor) — chame quando a operação tiver sucesso
  • reject(erro) — chame quando a operação falhar

Consumindo com .then() e .catch()

minhaPromise
  .then(function(resultado) {
    // Executado se a promise foi resolvida
    console.log(resultado); // "Operação concluída com sucesso!"
  })
  .catch(function(erro) {
    // Executado se a promise foi rejeitada
    console.error(erro.message);
  });

Com arrow functions — como você verá na prática:

minhaPromise
  .then(resultado => console.log(resultado))
  .catch(erro => console.error(erro.message));

Promise assíncrona — o caso real

function buscarUsuario(id) {
  return new Promise((resolve, reject) => {
    setTimeout(() => {
      if (id <= 0) {
        reject(new Error("ID inválido."));
        return;
      }

      resolve({
        id,
        nome: "Ana Paula",
        email: "ana@email.com",
        plano: "premium",
      });
    }, 1000);
  });
}

// Usando a função
buscarUsuario(42)
  .then(usuario => {
    console.log(`Olá, ${usuario.nome}!`);
    console.log(`Plano: ${usuario.plano}`);
  })
  .catch(erro => {
    console.error(`Erro: ${erro.message}`);
  });

buscarUsuario(-1)
  .then(usuario => console.log(usuario.nome))
  .catch(erro => console.error(`Erro: ${erro.message}`)); // Erro: ID inválido.

Compare com a versão em callback do artigo anterior — muito mais limpo.


Encadeamento de .then()

A grande vantagem das Promises: você pode encadear operações dependentes de forma linear, sem aninhar:

function buscarUsuario(id) {
  return new Promise(resolve => {
    setTimeout(() => resolve({ id, nome: "Carlos", enderecoId: 7 }), 500);
  });
}

function buscarEndereco(enderecoId) {
  return new Promise(resolve => {
    setTimeout(() => resolve({ id: enderecoId, cidade: "Curitiba", rua: "Av. Brasil" }), 400);
  });
}

function buscarPedidos(usuarioId) {
  return new Promise(resolve => {
    setTimeout(() => resolve([{ id: 101, total: 250 }, { id: 102, total: 180 }]), 600);
  });
}

// Encadeamento linear — sem pirâmide!
buscarUsuario(1)
  .then(usuario => {
    console.log(`Usuário: ${usuario.nome}`);
    return buscarEndereco(usuario.enderecoId); // retorna uma nova Promise
  })
  .then(endereco => {
    console.log(`Cidade: ${endereco.cidade}`);
    return buscarPedidos(1); // retorna uma nova Promise
  })
  .then(pedidos => {
    console.log(`Pedidos: ${pedidos.length}`);
    const total = pedidos.reduce((acc, p) => acc + p.total, 0);
    console.log(`Total: R$ ${total}`);
  })
  .catch(erro => {
    // Um único catch trata erros de QUALQUER etapa da cadeia
    console.error(`Algo deu errado: ${erro.message}`);
  });

Antes eram 4 níveis aninhados de callbacks. Agora é uma cadeia linear e legível. O catch no final captura qualquer erro de qualquer etapa.


Como o encadeamento funciona

Cada .then() retorna uma nova Promise. O valor retornado dentro do .then() se torna o valor resolvido da próxima Promise na cadeia:

Promise.resolve(1)
  .then(valor => {
    console.log(valor); // 1
    return valor + 1;   // retorna 2
  })
  .then(valor => {
    console.log(valor); // 2
    return valor * 3;   // retorna 6
  })
  .then(valor => {
    console.log(valor); // 6
  });

Se você retornar uma Promise dentro do .then(), o encadeamento espera essa Promise resolver antes de chamar o próximo .then().


.finally() — executar sempre

Assim como o finally do try/catch, ele executa independente de sucesso ou falha:

function carregarDados() {
  mostrarLoading(true);

  return buscarUsuario(1)
    .then(usuario => {
      exibirUsuario(usuario);
    })
    .catch(erro => {
      exibirErro(erro.message);
    })
    .finally(() => {
      mostrarLoading(false); // sempre oculta o loading
    });
}

Promise.resolve() e Promise.reject()

Atalhos para criar Promises já resolvidas ou rejeitadas:

// Já resolvida
Promise.resolve("valor imediato")
  .then(v => console.log(v)); // "valor imediato"

// Já rejeitada
Promise.reject(new Error("falha imediata"))
  .catch(e => console.error(e.message)); // "falha imediata"

// Útil para tornar funções síncronas compatíveis com código assíncrono
function obterConfiguracao(chave) {
  const cache = { tema: "escuro", idioma: "pt-BR" };

  if (cache[chave]) {
    return Promise.resolve(cache[chave]); // retorna Promise mesmo sendo síncrono
  }

  return buscarConfiguracaoDoServidor(chave); // assíncrono real
}

Promise.all() — executar em paralelo

Executa múltiplas Promises ao mesmo tempo e aguarda todas terminarem:

const promessas = [
  buscarUsuario(1),
  buscarUsuario(2),
  buscarUsuario(3),
];

Promise.all(promessas)
  .then(usuarios => {
    // usuarios é um array com os resultados na mesma ordem
    console.log(`${usuarios.length} usuários carregados`);
    usuarios.forEach(u => console.log(`- ${u.nome}`));
  })
  .catch(erro => {
    // Se QUALQUER uma falhar, cai aqui
    console.error(`Falha: ${erro.message}`);
  });

Promise.all é essencial para performance — em vez de fazer 3 requisições em sequência (3 segundos), faz as 3 ao mesmo tempo (~1 segundo, o tempo da mais lenta).


Promise.allSettled() — quando quer todos os resultados

Diferente do Promise.all, o allSettled não falha se uma Promise rejeitar — retorna o resultado de todas, sejam sucessos ou falhas:

const promises = [
  buscarUsuario(1),    // vai funcionar
  buscarUsuario(-1),   // vai rejeitar
  buscarUsuario(3),    // vai funcionar
];

Promise.allSettled(promises)
  .then(resultados => {
    resultados.forEach((resultado, i) => {
      if (resultado.status === "fulfilled") {
        console.log(`✅ Usuário ${i + 1}: ${resultado.value.nome}`);
      } else {
        console.log(`❌ Usuário ${i + 1}: ${resultado.reason.message}`);
      }
    });
  });

// ✅ Usuário 1: Ana Paula
// ❌ Usuário 2: ID inválido.
// ✅ Usuário 3: Ana Paula

Promise.race() — o mais rápido vence

Resolve ou rejeita assim que a primeira Promise terminar:

const timeout = new Promise((_, reject) =>
  setTimeout(() => reject(new Error("Timeout: requisição muito lenta.")), 3000)
);

const requisicao = buscarUsuario(1);

Promise.race([requisicao, timeout])
  .then(usuario => console.log(`Carregado: ${usuario.nome}`))
  .catch(erro => console.error(erro.message));
// Se buscarUsuario demorar mais de 3s → "Timeout: requisição muito lenta."
// Se terminar antes → exibe o usuário

Isso é um padrão muito útil para implementar timeout em requisições.


Promise.any() — o primeiro sucesso

Resolve com o primeiro que tiver sucesso. Só rejeita se todos falharem:

// Tentar múltiplos servidores — usa o que responder primeiro
const servidores = [
  buscarDoServidor("servidor-1"),
  buscarDoServidor("servidor-2"),
  buscarDoServidor("servidor-3"),
];

Promise.any(servidores)
  .then(dados => console.log("Dados recebidos:", dados))
  .catch(erro => console.error("Todos os servidores falharam."));

Tratamento de erros em Promises

// ✅ Um catch no final trata toda a cadeia
buscarUsuario(1)
  .then(usuario => buscarPedidos(usuario.id))
  .then(pedidos => processarPedidos(pedidos))
  .catch(erro => console.error(erro.message));

// ✅ Catch intermediário — recupera e continua
buscarConfiguracoes()
  .catch(erro => {
    console.warn("Configurações não encontradas. Usando padrões.");
    return { tema: "claro", idioma: "pt-BR" }; // valor de fallback
  })
  .then(config => {
    // config é o valor real OU o fallback
    aplicarConfiguracoes(config);
  });

// ⚠️ Promessa rejeitada sem catch = UnhandledPromiseRejection
// Sempre adicione .catch() nas suas Promises
buscarUsuario(-1); // ⚠️ sem catch — aviso no console
buscarUsuario(-1).catch(e => console.error(e)); // ✅

Exemplo completo — Dashboard de dados

// Funções que retornam Promises (simulando APIs)
function buscarEstatisticas() {
  return new Promise(resolve =>
    setTimeout(() => resolve({
      usuarios: 1284,
      pedidos: 342,
      receita: 48750.90,
      crescimento: 12.4,
    }), 800)
  );
}

function buscarUltimosPedidos() {
  return new Promise(resolve =>
    setTimeout(() => resolve([
      { id: 1001, cliente: "Ana", total: 250, status: "entregue" },
      { id: 1002, cliente: "Bruno", total: 180, status: "enviado" },
      { id: 1003, cliente: "Clara", total: 95, status: "pendente" },
    ]), 600)
  );
}

function buscarAlertas() {
  return new Promise(resolve =>
    setTimeout(() => resolve([
      { tipo: "aviso", mensagem: "Estoque baixo: Produto #42" },
      { tipo: "info", mensagem: "3 novos usuários hoje" },
    ]), 400)
  );
}

function buscarMetasMes() {
  return new Promise(resolve =>
    setTimeout(() => resolve({
      meta: 50000,
      atual: 48750.90,
      porcentagem: 97.5,
    }), 700)
  );
}

// Carrega tudo em paralelo — eficiente!
function carregarDashboard() {
  console.log("⏳ Carregando dashboard...");
  const inicio = Date.now();

  Promise.all([
    buscarEstatisticas(),
    buscarUltimosPedidos(),
    buscarAlertas(),
    buscarMetasMes(),
  ])
    .then(([stats, pedidos, alertas, metas]) => {
      const tempo = Date.now() - inicio;
      console.log(`✅ Dashboard carregado em ${tempo}ms\n`);

      // Estatísticas
      console.log("📊 ESTATÍSTICAS");
      console.log(`  Usuários: ${stats.usuarios.toLocaleString("pt-BR")}`);
      console.log(`  Pedidos: ${stats.pedidos}`);
      console.log(`  Receita: R$ ${stats.receita.toLocaleString("pt-BR", { minimumFractionDigits: 2 })}`);
      console.log(`  Crescimento: +${stats.crescimento}%\n`);

      // Meta do mês
      console.log("🎯 META DO MÊS");
      const barraSize = 20;
      const preenchido = Math.round((metas.porcentagem / 100) * barraSize);
      const barra = "█".repeat(preenchido) + "░".repeat(barraSize - preenchido);
      console.log(`  [${barra}] ${metas.porcentagem}%`);
      console.log(`  R$ ${metas.atual.toLocaleString("pt-BR")} de R$ ${metas.meta.toLocaleString("pt-BR")}\n`);

      // Últimos pedidos
      console.log("🛍️ ÚLTIMOS PEDIDOS");
      pedidos.forEach(p => {
        const icone = { entregue: "✅", enviado: "📦", pendente: "⏳" }[p.status];
        console.log(`  ${icone} #${p.id} — ${p.cliente}: R$ ${p.total}`);
      });

      // Alertas
      if (alertas.length > 0) {
        console.log("\n🔔 ALERTAS");
        alertas.forEach(a => {
          const icone = a.tipo === "aviso" ? "⚠️" : "ℹ️";
          console.log(`  ${icone} ${a.mensagem}`);
        });
      }
    })
    .catch(erro => {
      console.error(`❌ Erro ao carregar dashboard: ${erro.message}`);
    })
    .finally(() => {
      console.log("\n── Fim do carregamento ──");
    });
}

carregarDashboard();

Comparativo: Callbacks vs Promises

// ── Callbacks ──────────────────────────────
buscarUsuario(1, function(err, usuario) {
  if (err) return tratarErro(err);
  buscarPedidos(usuario.id, function(err, pedidos) {
    if (err) return tratarErro(err);
    processarPedidos(pedidos, function(err, resultado) {
      if (err) return tratarErro(err);
      console.log(resultado);
    });
  });
});

// ── Promises ───────────────────────────────
buscarUsuario(1)
  .then(usuario => buscarPedidos(usuario.id))
  .then(pedidos => processarPedidos(pedidos))
  .then(resultado => console.log(resultado))
  .catch(erro => tratarErro(erro));

A diferença é clara. E ainda vai melhorar no próximo artigo, quando chegarmos ao async/await.


Tarefa para você

Implemente um sistema de checkout de e-commerce com Promises:

// Implemente as funções abaixo retornando Promises com setTimeout:

// validarCarrinho(itens)
//   → resolve com itens se válido
//   → rejeita se carrinho estiver vazio

// verificarEstoque(itens)
//   → resolve com itens se todos têm estoque
//   → rejeita com qual produto está sem estoque

// calcularFrete(cep)
//   → resolve com { valor, prazo }
//   → rejeita se CEP inválido (menos de 8 dígitos)

// processarPagamento(total, cartao)
//   → resolve com { aprovado: true, codigo }
//   → rejeita se cartao.limite < total

// confirmarPedido(dados)
//   → resolve com { numeroPedido, previsaoEntrega }

// Monte a cadeia com Promise.all onde possível
// (verificarEstoque e calcularFrete podem ser paralelos)
// Trate cada tipo de erro com mensagem específica

Conclusão

Neste artigo você aprendeu:

  • O que é uma Promise e seus três estados
  • Como criar Promises com new Promise(resolve, reject)
  • Consumir com .then(), .catch() e .finally()
  • Encadear operações de forma linear e legível
  • Promise.all para operações em paralelo
  • Promise.allSettled quando quer todos os resultados
  • Promise.race para timeout e corrida
  • Promise.any para o primeiro sucesso
  • Tratamento de erros e fallbacks
  • Como Promises resolvem o Callback Hell

 

📚 Fontes e Referências

 

Comentários

Mais em Javascript

Revisão  + Projeto Final: Produção Real
Revisão + Projeto Final: Produção Real

Chegamos ao fim de mais um módulo. Em três artigos cobrimos deploy, segurança...

Tratamento de erros em requisições HTTP
Tratamento de erros em requisições HTTP

Fazer uma requisição funcionar no ambiente de desenvolvimento é fácil. O verd...

Async/Await: escrevendo código assíncrono de forma limpa
Async/Await: escrevendo código assíncrono de forma limpa

As Promises resolveram o Callback Hell. Mas encadear muitos&nbsp;.then() aind...