Callbacks e promises resolvem o mesmo problema: código que depende de uma operação assíncrona, como uma requisição de rede ou leitura de arquivo. A diferença está em como você organiza esse fluxo. Callbacks são funções passadas como argumento. Promises são objetos que representam um valor futuro, aos quais você anexa reações com .then() e .catch(). A escolha entre um e outro depende do tamanho do fluxo, da necessidade de reuso e da tolerância a erros silenciosos.
Legibilidade do código
Callbacks funcionam bem em fluxos curtos. Um único setTimeout ou um addEventListener não exige promise. O problema aparece quando uma operação depende da outra. Três níveis de aninhamento já tornam o código difícil de acompanhar, o que a comunidade chama de callback hell. Promises, com encadeamento plano via .then(), mantêm cada etapa em uma linha, sem aumentar a indentação. Para quem leu o código depois, o fluxo fica mais próximo de uma sequência linear.
Tratamento de erros
Em callbacks, o erro é convencionado como primeiro argumento da função, seguindo o padrão do Node.js. Se o desenvolvedor esquecer de tratar esse parâmetro, a falha passa despercebida. Em promises, o erro pode ser capturado em um único .catch() no final da cadeia. Esse comportamento reduz a chance de exceção não tratada, mas não elimina a responsabilidade de verificar rejeições em cada elo.
Encadeamento e composição
Promises permitem combinar operações paralelas com Promise.all, o que é difícil de reproduzir com callbacks puros. Em um cenário onde duas requisições independentes devem terminar antes de prosseguir, a promise entrega isso com poucas linhas. Callbacks exigem contadores manuais ou bibliotecas auxiliares. Por outro lado, callbacks têm menor custo de aprendizado para quem está começando, pois não exigem entender o ciclo de vida de um objeto promise.
Compatibilidade e contexto
Callbacks são suportados em qualquer ambiente JavaScript, inclusive em versões antigas de navegadores. Promises são nativas desde o ECMAScript 2015, mas exigem polyfill em ambientes legados. Em APIs de baixo nível, como as de alguns módulos do Node.js, callbacks ainda são o padrão. Em código novo, especialmente com async/await, promises são mais idiomáticas. A decisão também depende do time: se todos dominam promises, forçar callbacks por tradição não faz sentido.
Tabela comparativa
| Critério | Callbacks | Promises | |---|---|---| | Curva de aprendizado | Baixa | Média | | Legibilidade em fluxos curtos | Boa | Boa | | Legibilidade em fluxos encadeados | Ruim (callback hell) | Boa | | Tratamento de erros | Manual, propenso a omissão | Centralizado via .catch() | | Composição paralela | Complexa | Nativa com Promise.all | | Compatibilidade | Universal | ES2015+ (com polyfill) |
Veredito
Para uma única operação assíncrona ou integração com API que já usa callbacks, a abordagem com callbacks é suficiente e direta. Para fluxos com múltiplas etapas, tratamento de erro robusto ou execução paralela, promises são a escolha mais segura. Se você está começando um projeto novo em JavaScript moderno, promises combinadas com async/await tendem a produzir código mais manutenível. Callbacks continuam relevantes em bibliotecas legadas, mas não são o caminho recomendado para arquiteturas novas.
Perguntas frequentes
Callbacks são mais rápidos que promises?
Em operações isoladas, a diferença de desempenho é mínima e raramente perceptível. Promises adicionam uma camada de abstração que pode impactar benchmarks extremos, mas em aplicações reais o gargalo costuma estar na operação assíncrona em si, não na forma de lidar com ela.
Posso misturar callbacks e promises no mesmo código?
Pode, e isso é comum. Muitas APIs antigas ainda usam callbacks. Você pode envolver uma função de callback em uma promise com new Promise(), o que facilita a integração em fluxos modernos. O oposto, converter promise em callback, também é possível, mas menos frequente.
O que é callback hell exatamente?
É o aninhamento excessivo de callbacks dentro de callbacks, que ocorre quando cada operação assíncrona depende da anterior. O código fica com indentação profunda, difícil de ler e de manter. Promises resolvem isso com encadeamento plano.
Async/await substitui promises?
Não. async/await é uma sintaxe que usa promises por baixo. Ela torna o código assíncrono mais parecido com síncrono, mas você ainda precisa entender promises para lidar com rejeições e composição. Na prática, async/await é a forma recomendada de consumir promises.
Quando devo preferir callbacks em vez de promises?
Quando a API que você está usando já é baseada em callbacks e o fluxo é simples, como um único setTimeout ou um listener de evento. Também faz sentido em ambientes muito antigos sem suporte a promises. Fora disso, promises oferecem mais segurança e clareza.