Getting Started with Callbacks in JavaScript

In Brief
A callback is a function supplied to other code so that it can be invoked at the appropriate point, often after an operation or in response to an event. Callbacks are not inherently asynchronous; array methods use them synchronously too. They remain fundamental even where promises and async functions provide a clearer way to compose asynchronous work.
A callback is a function supplied to another piece of code for that code to invoke. It may run synchronously, asynchronously, or in response to an event or condition. That range is why callbacks appear in everything from array methods to timers and browser events.
Here, I'll explore what callbacks are, how they work, and how to use them effectively in your code. I'll also compare callbacks to other asynchronous programming strategies, such as Promises and the forthcoming async/await, and discuss some potential issues that developers may come across.
What are Callbacks?
In JavaScript, a callback is a function supplied to another function or API for that code to invoke. The simple example below invokes its callback synchronously. Other callbacks run later because a timer, network operation, event, or another condition determines when they are called.
Let's start with a simple example:
const greet = (name, callback) => {
console.log(`Hello, ${name}!`);
callback();
};
greet('Brian', function () {
console.log('The greeting was displayed.');
});Here, the greet function takes two arguments: a name and a callback function. The greet function logs a greeting into the console and then calls the callback function, demonstrating that the function is complete.
When we call the greet function with the name 'Brian' and a callback function that logs a message to the console (as shown above), we see the following output:
//=> Hello, Brian!
//=> The greeting was displayed.As you can see, the callback function is executed after the greeting (from greet) is logged to the console.
Callbacks vs. Promises
Callbacks were the original way to handle asynchronous operations in JavaScript, but Promises have become more popular in recent years. Promises provide a more elegant way to handle asynchronous operations and avoid the problem of "callback hell" where deeply nested callbacks can make code difficult to read and maintain.
I talk about promises in much more detail here, but for full coverage, here's an example of using a Promise to load data from a server:
fetch('https://api.example.com/data')
.then((response) => response.json())
.then((data) => console.log(data))
.catch((error) => console.error(error));In this example, the fetch function returns a Promise that resolves with the response from the server. We chain then methods to the Promise to handle the response data and catch any errors that may occur.
Callbacks vs. async/await
At the moment, async/await is still in the draft stage, but the documentation suggests that async/await will offer even more concise and understandable asynchronous methods. async/await provides a cleaner syntax for handling Promises and makes code easier to read and maintain.
Here's an example of using async/await to load data from a server, with the obvious caveat that this syntax may yet change as the specification matures:
async function getData() {
try {
const response = await fetch('https://api.example.com/data');
const data = await response.json();
console.log(data);
} catch (error) {
console.error(error);
}
}
getData();In this example, we define an async function that uses the await keyword to wait for the Promise returned by fetch to resolve. We then use await again to wait for the response data to be parsed as JSON.
In Conclusion
Callbacks are an essential tool for web developers who need to handle asynchronous operations in their code. They are a powerful and relatively simple way to handle asynchronous tasks and can be used in a wide variety of contexts. However, callbacks can also lead to deeply nested code and make it difficult to read and maintain.
Asynchronous programming has evolved since callbacks were first introduced, and now there are several other strategies available (or soon to become available) to developers.
Right now, the most popular alternative to callbacks is promises (although async/await isn't far away). Promises provide a more elegant way to handle asynchronous operations, whilst async/await will provide a cleaner syntax for handling Promises.
Callbacks work naturally when another function or API decides when supplied behaviour should run. Their error convention needs to stay clear, and several dependent asynchronous callbacks quickly become difficult to compose. Promises are often the clearer boundary when callers need one eventual result that they can return, await, and handle themselves.
Postscript
June 2017: async functions and await became part of ECMAScript 2017. The body preserves the earlier May 2015 discussion, when that syntax was still a proposal. One practical qualification also applies to the examples: fetch() can fulfil with an HTTP error response. Check response.ok and throw or handle an unsuccessful status explicitly if the failure should reach .catch() or the surrounding catch block; those examples otherwise catch network, parsing and thrown errors.