Função callback em JavaScript: o que é e exemplos
Callback é a função que você passa sem os parênteses para outra chamar depois. De forEach a setTimeout, com o erro dos parênteses a mais reproduzido no Node.
Callback é uma função que você passa como argumento para outra função, que decide quando chamá-la. A regra que resolve 90% dos bugs de iniciante com callback cabe numa linha: passe o nome da função, sem os parênteses. Com parênteses você executa na hora e entrega o resultado; sem parênteses você entrega a função.
Isso é possível porque em JavaScript função é um valor, como número e string. Ela pode ser guardada numa variável, colocada num array e passada adiante — é a mesma propriedade que permite as function expressions da lição anterior.
Callback está em todo lugar: forEach, map, filter, sort, setTimeout,
addEventListener, leitura de arquivo. Você já usou dezenas antes de saber o
nome.
O callback é o comportamento entregue para depois. Essa tradução ajuda, mas a gente continua usando o nome correto porque é ele que aparece na documentação, nas APIs e nas mensagens de erro.
Entregar a receita, não o prato pronto
Se uma cozinha pede a receita para preparar o prato quando chegar um pedido,
entregar um prato já pronto resolve outro problema. A referência da função é a
receita: pode ser chamada no momento escolhido por map, setTimeout ou
addEventListener. O retorno da chamada é o prato que já ficou pronto. Callback
é a primeira coisa, não a segunda.
Antes de rodar o exemplo, marque qual função recebe outra e quem decide o momento da chamada. Depois acompanhe o log dentro e fora do callback e compare a ordem. Assim você verifica, sem decorar API, quem controla a execução.
A função orcar não sabe calcular frete. Ela recebe quem sabe:
function freteExpresso(peso) {
return 24.9 + peso * 3;
}
function fretePac(peso) {
return 12.5 + peso * 1.2;
}
function orcar(peso, calculadora) {
return `R$ ${calculadora(peso).toFixed(2)}`;
}
console.log(orcar(2, freteExpresso));
console.log(orcar(2, fretePac));
console.log(orcar(2, (p) => 39.9 + p));Três chamadas, três regras de frete, uma função orcar só. O parâmetro
calculadora é o callback; freteExpresso, fretePac e a arrow anônima são os
valores que ele recebeu. Note que orcar não faz ideia de qual chegou — ela só
sabe que dá para pôr parênteses depois.
Esse desenho tem nome: inversão de controle. Quem escreve orcar decide o
esqueleto; quem chama decide o detalhe. É a mesma ideia por trás de map,
filter e sort, todos implementados assim por dentro.
Os métodos de array são callbacks o tempo todo
const itens = [
{ nome: 'Teclado mecânico', preco: 289.9 },
{ nome: 'Cadeira gamer', preco: 1199 },
{ nome: 'Mouse sem fio', preco: 149.9 },
];
itens.forEach((item, indice) => console.log(`${indice + 1}. ${item.nome}`));
console.log(itens.map((item) => item.nome));
console.log(itens.filter((item) => item.preco < 300).length, 'itens abaixo de 300');
console.log([...itens].sort((a, b) => a.preco - b.preco).map((i) => i.preco));Repare que quem chama o seu callback é o forEach, e é ele que decide os
argumentos: item, índice e o array inteiro, nessa ordem. Você recebe os que
quiser — declarar menos parâmetros do que o esperado é permitido, como mostra a
lição de parâmetros e retorno.
O sort é o único da lista que muta o array original, por isso a cópia com
[...itens]. E o callback dele não devolve verdadeiro ou falso: devolve um
número negativo, zero ou positivo.
setTimeout: o callback que roda depois
Aqui aparece a característica que separa callback síncrono de assíncrono. A função entra numa fila e só é chamada quando a linha principal termina.
console.log('1. pedido criado');
setTimeout(function confirmarPagamento() {
console.log('3. pagamento confirmado');
}, 50);
console.log('2. e-mail de confirmação enfileirado');Os números da saída não estão na ordem em que aparecem no arquivo — estão na
ordem em que o motor executou. Mesmo com 0 no lugar de 50, o resultado seria
o mesmo: setTimeout agenda para depois do código atual, não para “agora
mesmo”.
Repare também que dei um nome à função passada: confirmarPagamento. Ela não
precisava de nome, mas quando algo estourar lá dentro, o rastro vai dizer
at confirmarPagamento em vez de at <anonymous>.
O erro no navegador: addEventListener com parênteses
Este é o clássico. O botão é registrado e a página parece morta — ou pior, a ação acontece antes de qualquer clique.
<button id="finalizar">Finalizar compra</button>
<script type="module">
const botao = document.querySelector('#finalizar');
function finalizarCompra() {
console.log('pedido enviado');
}
botao.addEventListener('click', finalizarCompra()); // ERRADO
botao.addEventListener('click', finalizarCompra); // certo
</script>O EventTarget do navegador também existe no Node, então dá para reproduzir o
comportamento no terminal:
const botaoFinalizar = new EventTarget();
function finalizarCompra() {
console.log('pedido enviado');
}
botaoFinalizar.addEventListener('click', finalizarCompra());
botaoFinalizar.dispatchEvent(new Event('click'));Leia a saída na ordem. pedido enviado apareceu antes de qualquer clique —
foi a chamada com parênteses, executada na hora do registro. O que ficou
registrado como listener foi o retorno dela, undefined. Depois, o
dispatchEvent disparou o evento e nada aconteceu.
O Node avisa; o navegador não diz absolutamente nada. Corrigindo:
const botaoFinalizar = new EventTarget();
function finalizarCompra(evento) {
console.log('pedido enviado no evento', evento.type);
}
botaoFinalizar.addEventListener('click', finalizarCompra);
botaoFinalizar.dispatchEvent(new Event('click'));Erro primeiro: a convenção do Node
Antes das Promises, toda API assíncrona do Node seguia a mesma assinatura de callback: o primeiro parâmetro é o erro, o segundo é o resultado.
import { readFile } from 'node:fs';
function carregarPedido(arquivo, pronto) {
readFile(arquivo, 'utf8', (erro, conteudo) => {
if (erro) return pronto(erro);
pronto(null, JSON.parse(conteudo));
});
}
carregarPedido('/private/tmp/loja/pedido-7412.json', (erro, pedido) => {
if (erro) return console.log('falhou:', erro.code);
console.log('total do pedido:', pedido.total);
});
carregarPedido('/private/tmp/loja/pedido-9999.json', (erro, pedido) => {
if (erro) return console.log('falhou:', erro.code);
console.log('total do pedido:', pedido.total);
});A saída inverteu a ordem das chamadas, e isso é informação real: o arquivo que não existe falha mais rápido do que o arquivo que precisa ser lido do disco. Com callback, a ordem de conclusão não é a ordem do código — nunca conte com ela.
O if (erro) return no começo de cada callback não é frescura de estilo. Sem o
return, o código continua e tenta ler pedido.total de um undefined.
Callback hell
O problema aparece quando uma etapa depende da anterior. Cada dependência adiciona um nível:
function buscarCliente(id, pronto) {
setTimeout(() => pronto({ id, nome: 'Ana Souza' }), 10);
}
function buscarCarrinho(cliente, pronto) {
setTimeout(() => pronto({ cliente: cliente.nome, total: 479.7 }), 10);
}
function calcularFrete(carrinho, pronto) {
setTimeout(() => pronto({ ...carrinho, frete: carrinho.total >= 199 ? 0 : 24.9 }), 10);
}
buscarCliente(31, (cliente) => {
buscarCarrinho(cliente, (carrinho) => {
calcularFrete(carrinho, (fechado) => {
console.log(fechado);
});
});
});Funciona. Com três etapas ainda dá para ler. Com seis, mais tratamento de erro em cada nível, vira a pirâmide que ficou conhecida como callback hell — e o motivo real não é a indentação, é que não existe um lugar único para tratar falha.
O mesmo fluxo com Promise e await fica plano:
const esperar = (ms, valor) => new Promise((resolver) => setTimeout(() => resolver(valor), ms));
const buscarCliente = (id) => esperar(10, { id, nome: 'Ana Souza' });
const buscarCarrinho = (cliente) => esperar(10, { cliente: cliente.nome, total: 479.7 });
const calcularFrete = (c) => esperar(10, { ...c, frete: c.total >= 199 ? 0 : 24.9 });
const cliente = await buscarCliente(31);
const carrinho = await buscarCarrinho(cliente);
console.log(await calcularFrete(carrinho));Mesmo resultado, sem aninhamento. Repare que o callback não sumiu: ele está
dentro do new Promise, na função que recebe resolver. Promise é uma camada
por cima de callback, não uma substituta dele.
Erros comuns: os parênteses a mais
Fora do navegador, passar o resultado no lugar da função não dá aviso nenhum — dá exceção, e o rastro aponta para o lugar errado:
function freteExpresso(peso) {
return 24.9 + peso * 3;
}
function orcar(peso, calculadora) {
return `R$ ${calculadora(peso).toFixed(2)}`;
}
console.log(orcar(2, freteExpresso(2)));TypeError: calculadora is not a function at orcar (file:///private/tmp/loja/callback-parenteses.mjs:6:16) at file:///private/tmp/loja/callback-parenteses.mjs:9:13 at ModuleJob.run (node:internal/modules/esm/module_job:439:25)
Node.js v24.16.0
O erro cita calculadora, que é o nome do parâmetro — e não freteExpresso,
que é onde está o defeito. Quem lê rápido vai investigar orcar, que está
correta. A pista de que o problema é na chamada está na segunda linha do rastro:
at file:...:9:13 é a linha 9, a do console.log.
Quando você vir X is not a function e X for um parâmetro, olhe a chamada, não
a função. Quase sempre são parênteses sobrando.
Do callback ao próximo passo
Três hábitos que evitam a maior parte dos problemas com callback:
- Nomeie o callback quando ele passar de duas linhas. Uma arrow anônima de
quinze linhas dentro de um
addEventListeneré impossível de testar e some do rastro de erro. - Trate o erro no próprio callback e saia com
return. Nunca deixe o fluxo continuar depois de detectar falha. - Não misture callback e Promise na mesma função. Ou ela recebe um callback, ou ela devolve uma Promise. As duas coisas ao mesmo tempo produzem o bug de chamar o callback duas vezes.
Quando o callback captura variáveis do escopo onde foi escrito — e ele quase sempre captura — o que está em jogo é closure, a lição que explica por que a função lembra de valores que já deveriam ter sumido. E quando o callback é um método de objeto, entra o problema de this em JavaScript. O guia completo de JavaScript mostra em que ponto da trilha cada um desses assuntos entra.
Perguntas frequentes
Por que o callback vai sem parênteses?
Todo callback é assíncrono?
Quando o callback vira problema?
Posso passar um método de objeto como callback?
Dúvidas e comentários
Travou em algum passo? Pergunte aqui — a equipe e outros alunos respondem.
Entrar para perguntarÉ o mesmo login gratuito dos cursos.
Nenhuma dúvida por aqui ainda — a primeira pode ser a sua.
Todo o código deste artigo foi executado em Node 24.16.0, e as saídas exibidas são as reais — como produzimos este conteúdo.
Fontes consultadas
- MDN — Callback function — developer.mozilla.org
- MDN — EventTarget.addEventListener — developer.mozilla.org
- Node.js — Asynchronous flow control — nodejs.org


