JavaScriptCallbacks

Callbacks

A callback is a function you hand to another function so it can call it back later. Callbacks come in two flavours that look identical but behave very differently: synchronous callbacks (called immediately during execution) and asynchronous callbacks (called later, after some external event). Understanding which kind you are using is essential for reasoning about order.

Synchronous callbacks

A synchronous callback is invoked while the function you handed it to is running. The classic examples are array methods.

JS
const nums = [1, 2, 3];

console.log("before");
nums.forEach(n => console.log("  visit", n));
console.log("after");
before
  visit 1
  visit 2
  visit 3
after

The callback runs immediately, three times, before forEach returns. Nothing is queued for later — it is just a way to pass behaviour into another function.

Asynchronous callbacks

An asynchronous callback is handed to an API that says "I'll call this later, when something happens" — a timer firing, a network response arriving, a user clicking. Your function returns immediately; the callback runs at some future point on the event loop.

JS
console.log("before");

setTimeout(() => {
  console.log("  delayed");
}, 1000);

console.log("after");
before
after
  delayed     (~1 second later)

setTimeout registers the callback with the host environment and returns. The "after" log happens next. The browser fires the timer ~1s later and queues the callback for the event loop.

Common asynchronous callback APIs
  • TimerssetTimeout, setInterval, requestAnimationFrame.

  • DOM eventselement.addEventListener("click", handler).

  • Network (older APIs)XMLHttpRequest, Node's file system fs.readFile(path, cb).

  • Node patterns — almost everything in classic Node took a callback (err, result) => ....

Classic Node error-first pattern

JS
// fs.readFile("config.json", "utf8", (err, data) => {
//   if (err) return console.error(err);
//   console.log("got config:", data);
// });
Why order is sometimes surprising

The single most common bug for beginners is treating asynchronous callbacks as if they were synchronous:

JS
function loadUser(id) {
  let result;
  setTimeout(() => { result = { id, name: "Ada" }; }, 100);
  return result;       // returns undefined — the timeout hasn't fired yet!
}

console.log(loadUser(1));   // undefined

Anything that depends on the async result must happen inside the callback (or use a Promise / async/await):

JS
function loadUser(id, cb) {
  setTimeout(() => cb({ id, name: "Ada" }), 100);
}

loadUser(1, user => {
  console.log(user);    // runs ~100ms later
});
Callback hell — what we used to live with

Before Promises and async/await, chaining several asynchronous steps led to deeply nested code — the famous "pyramid of doom":

JS
getUser(id, (err, user) => {
  if (err) return done(err);
  getOrders(user.id, (err, orders) => {
    if (err) return done(err);
    getInvoice(orders[0].id, (err, invoice) => {
      if (err) return done(err);
      sendEmail(user.email, invoice, (err) => {
        if (err) return done(err);
        done(null, "all good");
      });
    });
  });
});

Each level adds another indentation, another if (err) block, and another place where you can forget to handle an error. Promises and async/await flatten this dramatically — we cover them in Promises and async/await.

Writing your own callback-style API

Sometimes you still write callbacks — most often for synchronous "configure this behaviour" helpers.

JS
function repeat(times, callback) {
  for (let i = 0; i < times; i++) callback(i);
}

repeat(3, i => console.log("step", i));
step 0
step 1
step 2
One callback, not two
If you write an async function that accepts a callback, make sure you call it **exactly once**. Calling it twice (e.g. once on success and once on error you forgot to return from) creates very confusing bugs.
When you see a callback, ask: sync or async?
Knowing whether a function will call your callback *now* or *later* is the first step to reasoning about ordering. Array methods, `forEach`, sort comparators — synchronous. Timers, network, events, Node fs APIs — asynchronous.