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

Objetos: estruturando dados do mundo real
Objetos: estruturando dados do mundo real

Arrays s&atilde;o &oacute;timos para listas. Mas como representar uma&nbsp;pe...

Mini Projeto: Quiz Interativo
Mini Projeto: Quiz Interativo

Chegamos ao fim do M&oacute;dulo 2. Em sete artigos voc&ecirc; aprendeu a man...

Trabalhando com JSON
Trabalhando com JSON

JSON est&aacute; em absolutamente tudo. &Eacute; o formato de dados mais usad...