Callback in JavaScript: passing a function to be run later

A callback is a function handed to another one to be called in due time: the error-first convention, the pyramid, and the way out through promises.
3 min read
Believemy logo

Before promises, the language still needed a way to say: here is what should happen once this is ready. The answer is a simple idea, and one as old as JavaScript itself.

You hand over the rest of the work as a function, and the code you called decides when to fire it.


Definition

A callback is a function passed as an argument to another function, which takes care of calling it. It does not run where you write it, it is handed over.

JAVASCRIPT
const users = ["Chloe", "Marc", "Ines"];

// Called right away, once per item
users.forEach((name) => console.log(name));

// Set aside, and called one second later
setTimeout(() => console.log("One second went by"), 1000);

Both lines go by the same name and do nothing alike. Array.forEach() calls back immediately, setTimeout() calls back later. The difference never shows in the syntax, only in the documentation of the function receiving it.


The error-first convention

An operation that can fail has to be able to report that failure. Node.js settled on a rule that became a standard: the callback receives the error as its first argument, the result as its second.

JAVASCRIPT
function readConfig(path, callback) {
  if (!path) return callback(new Error("Missing path"));
  callback(null, { theme: "dark" });
}

readConfig("app.json", (err, config) => {
  if (err) return console.log("Failed:", err.message);
  console.log(config.theme);   // dark
});

The null in first position means nothing broke. A callback that never tests err swallows failures in silence, and that is by far the most common defect of this style.


The pyramid, and why we left it behind

A callback inside a callback inside a callback: every step pushes the next one further right, and the error test repeats at each level.

JAVASCRIPT
readConfig("app.json", (err, config) => {
  if (err) return console.log(err.message);
  readProfile(config, (err, profile) => {
    if (err) return console.log(err.message);
    readInvoices(profile, (err, invoices) => {
      if (err) return console.log(err.message);
      console.log(invoices.length);
    });
  });
});

A Promise fixes both problems at once: the run of steps flattens back out with then(), and a single catch covers the whole chain. With async and await, only three lines remain and they read top to bottom.

Good to know

The callback has not gone away. Array methods such as Array.map() or Array.filter() take one, so does addEventListener, and anything that fires more than once still depends on it. Only single-result operations moved over to promises.


Frequently asked questions

Question

Is a callback always asynchronous?

No, and the confusion is a stubborn one. The callback given to forEach or map runs immediately, in place, before the next line. The one given to setTimeout waits its turn. The word says nothing about timing: you have to look at what the receiving function does with it.


Question

Why does my callback lose the value of this?

Because a classic function gets its this from the way it is called, and the code calling it back calls it with no context at all. An arrow function settles the matter: it has no this of its own and keeps the one from the place where it was written.


Question

Is this style still worth learning?

Yes, because it is everywhere: browser events, array methods, older libraries you still run into. Understanding callbacks also makes promises far clearer, since promises only organize the very same idea. Both styles are worked side by side in the JavaScript course.

Related terms

Discover our javaScript glossary

Every word of JavaScript explained simply: keywords, built-in objects, methods, errors and concepts. Clear definitions and examples that actually run, to learn and to troubleshoot.

Share this article

Want to help us? Share this article on your networks or even better: on your site, in an article or in your newsletter.