Um callback é uma função que você entrega para outra função
No JavaScript, funções são valores. Dá para guardá-las em variáveis, colocar dentro de arrays e — o que interessa aqui — passá-las como argumento. Quando você passa uma função para outra função chamar depois, essa função que você passou é um callback.
greet não sabe nem se importa com o que o formatter faz. Ele só chama a função passando um nome e usa o resultado. Quem escolhe o comportamento é você, passando callbacks diferentes. É justamente essa flexibilidade que justifica a existência dos callbacks.
Callbacks síncronos executam na hora
Nem todo callback é assíncrono. Vários métodos de array que você já usa no dia a dia são baseados em callback e rodam de forma síncrona — ou seja, o callback executa antes da chamada externa retornar:
map, filter e reduce recebem um callback e chamam essa função uma vez para cada elemento, ali na hora. Quando o map termina de rodar, todas as chamadas ao callback já aconteceram. Nada fica agendado para depois.
É o padrão clássico de função de ordem superior (higher-order function): "toma aqui o trabalho, toma aqui como fazer, me devolve o resultado". Sem event loop no meio.
Callback assíncrono em JavaScript: execução adiada
Quando o pessoal fala em "callback" no dia a dia, geralmente está se referindo aos callbacks assíncronos. A ideia é simples: você passa uma função para alguma API que demora um tempo — um timer, uma requisição de rede, a leitura de um arquivo — e essa API chama a sua função de volta assim que o trabalho termina.
A ordem de saída é: antes, depois e, um segundo depois, timer disparado. O setTimeout não pausa seu programa. Ele entrega o callback para o runtime, retorna na hora e o resto do script continua rodando. Um segundo depois, o event loop pega esse callback e executa.
Esse padrão de "retorna agora, chama depois" é o modelo mental de toda API de callback assíncrono em JavaScript, desde o addEventListener até as APIs mais antigas de arquivos do Node.js.
A convenção error-first (Node.js)
Antes das promises existirem, o Node.js padronizou um formato específico de callback: o primeiro argumento é um erro (ou null) e os demais são o resultado de verdade. Você ainda vai topar com esse estilo em códigos antigos e em algumas bibliotecas.
O código que chama verifica o err primeiro e sai logo de cara se ele for truthy. Só depois é que confia no resultado. É uma convenção — a linguagem não obriga nada disso — mas assim que você bate o olho na assinatura (err, result) => ..., reconhece o padrão em qualquer lugar.
Callback hell: quando os callbacks viram um pesadelo
O problema aparece quando um passo assíncrono depende do resultado de outro. Cada callback precisa ficar aninhado dentro do anterior, e o código vai formando uma escadinha que só cresce para o lado:
Essa é a famosa "pirâmide da perdição", também conhecida como callback hell. Alguns motivos deixam essa situação chata de lidar:
- O fluxo de controle vai ziguezagueando em vez de ser lido de cima para baixo.
- Cada nível repete o mesmo
if (err) return ...como boilerplate. - Quando um callback lança uma exceção, ela não se propaga para os de fora — você precisa tratar os erros em cada camada.
- Refatorar significa reindentar o bloco inteiro.
Dá para achatar um pouco extraindo funções nomeadas, mas o problema central — compor código assíncrono com callbacks puros é desajeitado — continua ali. Foi justamente isso que as promises vieram resolver.
Duas pegadinhas que vale a pena conhecer
Não chame o callback sem querer. Quando você passa um callback, você passa a própria função — e não o resultado de executá-la.
Cuidado com o this. Se o seu callback for uma função comum que usa this, o valor de this vai depender de como o callback é chamado, e não de onde ele foi definido. As arrow functions resolvem esse problema herdando o this do escopo em que foram criadas:
Arrow functions são a escolha padrão para callbacks inline justamente por esse motivo.
Callback vs Promise em JavaScript
As funções callback ainda aparecem bastante em APIs síncronas (map, forEach, sort), em listeners de eventos (element.addEventListener("click", ...)) e em hooks de baixo nível do runtime. Já para código assíncrono que produz um único resultado, o ecossistema migrou quase que totalmente para promises.
Uma comparação rápida:
- Callbacks — diretos, enxutos, mas compõem mal. O tratamento de erro é manual em cada etapa.
- Promises — um valor que representa um resultado futuro. Você encadeia com
.then(), trata erros num só ponto com.catch()e a pirâmide some.
Mesmo assim, entender callbacks continua sendo essencial: são a base sobre a qual as promises foram construídas, e estão por toda parte em código orientado a eventos. Só que, hoje em dia, raramente alguém escreve APIs assíncronas novas usando callbacks puros.
Próximo passo: Promises
As promises pegam a ideia de "faça isso quando aquilo estiver pronto" e embalam tudo num objeto que você pode passar adiante, encadear e compor. É esse o assunto da próxima página — e também a ponte para async/await, que é como a maior parte do JavaScript moderno lida com código assíncrono.
Perguntas frequentes
O que é uma função de callback em JavaScript?
Callback é uma função que você passa como argumento para outra função, para que ela seja chamada depois. Por exemplo, setTimeout(() => console.log('oi'), 1000) passa uma arrow function como callback — o setTimeout guarda essa função e chama ela quando o timer dispara. Os callbacks foram a forma original do JavaScript lidar com o clássico 'faça isso quando aquilo estiver pronto'.
Qual a diferença entre callback síncrono e assíncrono?
O callback síncrono roda na hora, durante a execução da função que o recebeu — [1, 2, 3].map(x => x * 2) chama o callback três vezes antes do map retornar. Já o callback assíncrono fica guardado e só é executado depois, quando algum evento acontece — é o caso do setTimeout, do fs.readFile e dos listeners de eventos do DOM. Callbacks assíncronos não travam o resto do código.
O que é callback hell e como evitar?
Callback hell é aquela pirâmide que se forma quando callbacks assíncronos dependem uns dos outros e vão ficando aninhados em vários níveis. O fluxo de execução e o tratamento de erros viram um pesadelo de ler. A solução é usar Promises com encadeamento de .then(), ou melhor ainda, async/await — os dois achatam a pirâmide e deixam o código legível de novo.