Callbacks em JavaScript: O Padrão Original de Assincronismo: Do Básico ao Avançado Já leu

Callbacks em JavaScript: O Padrão Original de Assincronismo O que é um Callback? 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". 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. Callbacks Básicos e Seu Funcionamento O exemplo mais comum é o , que executa uma função após um tempo determinado: Olá, ${nome}! Aqui, a função anônima é o callback. Você passa ela para , que a armazena e a executa depois. Outro exemplo

Callbacks em JavaScript: O Padrão Original de Assincronismo

O que é um Callback?

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".

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.

Callbacks Básicos e Seu Funcionamento

O exemplo mais comum é o setTimeout, que executa uma função após um tempo determinado:

function saudar(nome) {
  console.log(`Olá, ${nome}!`);
}

setTimeout(function() {
  saudar("Maria");
}, 2000); // Executa após 2 segundos

Aqui, a função anônima é o callback. Você passa ela para setTimeout, que a armazena e a executa depois. Outro exemplo prático é com Array:

const numeros = [1, 2, 3, 4, 5];

const dobrados = numeros.map(function(num) {
  return num * 2;
});

console.log(dobrados); // [2, 4, 6, 8, 10]

O callback aqui é a função passada ao map(). Esse padrão é tão comum que você provavelmente já usou sem perceber. A função callback recebe parâmetros (no caso, num) que o map() 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.

Callbacks com Operações Assíncronas Reais

O verdadeiro poder dos callbacks aparece quando você trabalha com operações que levam tempo. Vamos simular uma requisição a um servidor:

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);
});

Aqui, buscarUsuario() 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:

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);
});

Aqui temos o padrão error-first callback: o primeiro parâmetro é sempre o erro (ou null 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.

O Problema: Callback Hell

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:

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!");
    });
  });
});

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.

Nota importante: 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.

Boas Práticas com Callbacks

Se você precisa usar callbacks (especialmente em código existente), siga estas práticas:

Use nomes descritivos para deixar claro que é um callback:

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);
});

Sempre trate erros — nunca suponha que tudo correrá bem:

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);
  }
});

Evite callbacks aninhados — separe em funções nomeadas:

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);
    });
  });
});

Conclusão

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.

Referências


Artigos relacionados