<h2>Callbacks em JavaScript: O Padrão Original de Assincronismo</h2>
<h3>O que é um Callback?</h3>
<p>Um callback é simplesmente uma função passada como argumento para outra função, que será executada posteriormente — geralmente após a conclusão de uma operação assíncrona. Em JavaScript, isso é fundamental porque a linguagem é single-threaded: operações como requisições HTTP, leitura de arquivos ou timers não podem bloquear a execução do código. O callback permite que você diga ao JavaScript: "faça isto, e quando terminar, execute essa função".</p>
<p>Quando você compreende callbacks, você entende o núcleo da programação assíncrona em JavaScript. Mesmo com a chegada de Promises e async/await, callbacks continuam sendo o mecanismo subjacente. Se você vai dominar JavaScript profissional, precisa dessa base sólida.</p>
<h3>Callbacks Básicos e Seu Funcionamento</h3>
<p>O exemplo mais comum é o <code>setTimeout</code>, que executa uma função após um tempo determinado:</p>
<pre><code class="language-javascript">function saudar(nome) {
console.log(Olá, ${nome}!);
}
setTimeout(function() {
saudar("Maria");
}, 2000); // Executa após 2 segundos</code></pre>
<p>Aqui, a função anônima é o callback. Você passa ela para <code>setTimeout</code>, que a armazena e a executa depois. Outro exemplo prático é com Array:</p>
<pre><code class="language-javascript">const numeros = [1, 2, 3, 4, 5];
const dobrados = numeros.map(function(num) {
return num * 2;
});
console.log(dobrados); // [2, 4, 6, 8, 10]</code></pre>
<p>O callback aqui é a função passada ao <code>map()</code>. Esse padrão é tão comum que você provavelmente já usou sem perceber. A função callback recebe parâmetros (no caso, <code>num</code>) que o <code>map()</code> fornece automaticamente. Isso demonstra como callbacks são flexíveis: a função que recebe o callback controla quando executá-lo e com quais argumentos.</p>
<h3>Callbacks com Operações Assíncronas Reais</h3>
<p>O verdadeiro poder dos callbacks aparece quando você trabalha com operações que levam tempo. Vamos simular uma requisição a um servidor:</p>
<pre><code class="language-javascript">function buscarUsuario(id, callback) {
setTimeout(function() {
const usuario = { id: id, nome: "João" };
callback(usuario);
}, 1000);
}
buscarUsuario(1, function(usuario) {
console.log("Usuário encontrado:", usuario);
});</code></pre>
<p>Aqui, <code>buscarUsuario()</code> simula uma requisição que leva 1 segundo. O callback é executado com os dados quando prontos. Em cenários reais com Node.js, você verá este padrão frequentemente:</p>
<pre><code class="language-javascript">const fs = require('fs');
fs.readFile('dados.txt', 'utf8', function(erro, dados) {
if (erro) {
console.log("Erro ao ler arquivo:", erro);
return;
}
console.log("Conteúdo:", dados);
});</code></pre>
<p>Aqui temos o padrão <strong>error-first callback</strong>: o primeiro parâmetro é sempre o erro (ou <code>null</code> se não houver erro), e os dados vêm depois. Esse é o padrão de facto em Node.js e você o verá em muitas bibliotecas legadas.</p>
<h3>O Problema: Callback Hell</h3>
<p>Aqui está onde callbacks mostram sua limitação. Quando você precisa fazer várias operações sequenciais, o código fica aninhado em níveis cada vez mais profundos:</p>
<pre><code class="language-javascript">fs.readFile('usuarios.json', 'utf8', function(erro, dados) {
if (erro) throw erro;
const usuarios = JSON.parse(dados);
fs.readFile('permissoes.json', 'utf8', function(erro2, dados2) {
if (erro2) throw erro2;
const permissoes = JSON.parse(dados2);
fs.writeFile('resultado.json', JSON.stringify({usuarios, permissoes}), function(erro3) {
if (erro3) throw erro3;
console.log("Arquivo salvo!");
});
});
});</code></pre>
<p>Esse "callback hell" ou "pyramid of doom" é difícil de ler, manter e debugar. O tratamento de erros fica espalhado, e rastrear o fluxo se torna complicado. É exatamente por isso que JavaScript evoluiu: Promises surgiram para resolver esse problema, seguidas por async/await.</p>
<blockquote><p><strong>Nota importante</strong>: Você deve entender callbacks profundamente não para usá-los em novo código — embora ainda apareçam em algumas APIs — mas porque muitas bibliotecas antigas os usam, e você provavelmente trabalhar com código legado.</p></blockquote>
<h3>Boas Práticas com Callbacks</h3>
<p>Se você precisa usar callbacks (especialmente em código existente), siga estas práticas:</p>
<p><strong>Use nomes descritivos</strong> para deixar claro que é um callback:</p>
<pre><code class="language-javascript">function processarDados(dados, quandoTerminar) {
setTimeout(function() {
const resultado = dados.map(x => x * 2);
quandoTerminar(resultado);
}, 500);
}
processarDados([1, 2, 3], function(resultado) {
console.log(resultado);
});</code></pre>
<p><strong>Sempre trate erros</strong> — nunca suponha que tudo correrá bem:</p>
<pre><code class="language-javascript">function operacaoRisca(callback) {
setTimeout(function() {
const sucesso = Math.random() > 0.5;
if (sucesso) {
callback(null, "Operação bem-sucedida");
} else {
callback(new Error("Falha na operação"), null);
}
}, 1000);
}
operacaoRisca(function(erro, resultado) {
if (erro) {
console.error("Erro:", erro.message);
} else {
console.log(resultado);
}
});</code></pre>
<p><strong>Evite callbacks aninhados</strong> — separe em funções nomeadas:</p>
<pre><code class="language-javascript">function etapa1(callback) {
setTimeout(() => callback(null, "Dados 1"), 300);
}
function etapa2(dados1, callback) {
setTimeout(() => callback(null, dados1 + " + Dados 2"), 300);
}
function etapa3(dados2, callback) {
setTimeout(() => callback(null, dados2 + " = Resultado Final"), 300);
}
etapa1(function(erro, resultado1) {
if (erro) return console.error(erro);
etapa2(resultado1, function(erro, resultado2) {
if (erro) return console.error(erro);
etapa3(resultado2, function(erro, resultado3) {
if (erro) return console.error(erro);
console.log(resultado3);
});
});
});</code></pre>
<h2>Conclusão</h2>
<p>Callbacks são a base do assincronismo em JavaScript e ainda aparecem em muitas APIs nativas e bibliotecas. O padrão error-first callback é especialmente importante em Node.js. Embora Promises e async/await sejam superiores para novo código, dominar callbacks é essencial para compreender JavaScript profundamente e trabalhar com código legado com confiança. O principal aprendizado é que callbacks permitem executar código depois que uma operação termina, mas em cascatas complexas eles se tornam difíceis de manter — justamente por isso a linguagem evoluiu.</p>
<h2>Referências</h2>
<ul>
<li><a href="https://developer.mozilla.org/pt-BR/docs/Glossary/Callback_function" target="_blank" rel="noopener noreferrer">MDN - Callbacks</a></li>
<li><a href="https://nodejs.org/en/docs/guides/blocking-vs-non-blocking/" target="_blank" rel="noopener noreferrer">Node.js - Error-first Callbacks</a></li>
<li><a href="https://javascript.info/callbacks" target="_blank" rel="noopener noreferrer">JavaScript.info - Introduction to callbacks</a></li>
<li><a href="https://eloquentjavascript.net/11_async.html" target="_blank" rel="noopener noreferrer">Eloquent JavaScript - Asynchronous Programming</a></li>
<li><a href="https://github.com/getify/You-Dont-Know-JS/tree/2nd-ed/async-performance" target="_blank" rel="noopener noreferrer">You Don't Know JS - Async & Performance</a></li>
</ul>