How to Read Nested Callback Code in JavaScript

You understand what callbacks are and how they work. A callback is essentially a function that gets called when a task is completed.

But consider the code below:

readFile("docs.md", function (mdContent) {
  convertMarkdownToHTML(mdContent, function (htmlContent) {
    addCssStyles(htmlContent, function (docs) {
      saveFile(docs, "docs.html", function (result) {
        ftp.sync();
      });
    });
  });
});

How do you even begin to make sense of something like this? What mental framework should you use to understand callback code?

In this article, I’ll teach you how to read complicated code like this.

How Does Code Like This Get Written?

Understanding how this code gets written will help you develop the mental model you need to read and understand it—where to begin and how to make sense of it all.

Let’s start by imagining we are building a system for converting and processing markdown files. To illustrate this, let’s go through a simple example where we need to read a markdown file, convert it to HTML, apply CSS styles, save the result as an HTML file, and finally sync the file to an FTP server.

Step 1: Reading the File

We’ll begin by reading the file using a function readFile to read the markdown file docs.md.

readFile("docs.md");

For simplicity, we assume readFile reads the markdown content and returns it. In a real-world scenario, this process could be more complex, such as handling large files or reading from a remote source.

Step 2: Converting Markdown to HTML

Once we have the markdown content, the next step is to convert it to HTML using the convertMarkdownToHTML function.

convertMarkdownToHTML(mdContent);

Since this operation depends on the markdown content, we need to call convertMarkdownToHTML only after successfully reading the file. This introduces a dependency between reading the file and converting it.

Because these operations are asynchronous, a callback helps manage the flow of code. First, we wrap the convertMarkdownToHTML call inside a callback function:

function(mdContent) {
  convertMarkdownToHTML(mdContent);
}

Where will this callback function go? You guessed it—we pass this callback to readFile("docs.md");, like so:

readFile("docs.md", function (mdContent) {
  convertMarkdownToHTML(mdContent);
});

When the JavaScript engine runs this code, it first executes readFile. Once readFile finishes reading the file, it calls the provided callback function to execute convertMarkdownToHTML.

This is how we manage asynchronous operations in JavaScript.

Step 3: Applying CSS Styles

After converting the markdown to HTML, the next step is to apply CSS styles using the addCssStyles function.

addCssStyles(htmlContent);

Once again, we wrap this function inside a callback:

function(htmlContent) {
	addCssStyles(htmlContent);
}

We pass this callback function to convertMarkdownToHTML:

readFile("docs.md", function (mdContent) {
  convertMarkdownToHTML(mdContent, function (htmlContent) {
    addCssStyles(htmlContent);
  });
});

Step 4: Saving the File

After applying the CSS, we need to save the file as an HTML document. We’ll use the saveFile function for this.

saveFile(docs);

This operation should only happen after the CSS has been applied. To manage this, we wrap saveFile in a callback and pass it to addCssStyles.

readFile("docs.md", function (mdContent) {
  convertMarkdownToHTML(mdContent, function (htmlContent) {
    addCssStyles(htmlContent, function (docs) {
      saveFile(docs);
    });
  });
});

Step 5: Syncing to FTP

Finally, after saving the file, we sync it with the FTP server using the ftp.sync function. Again, this function is called only after the file has been saved.

ftp.sync();

We use a callback for this too, ensuring everything happens in sequence.

readFile("docs.md", function (mdContent) {
  convertMarkdownToHTML(mdContent, function (htmlContent) {
    addCssStyles(htmlContent, function (docs) {
      saveFile(docs, "docs.html", function (result) {
        ftp.sync();
      });
    });
  });
});

How to Read Callback Code

While writing the above code, you might have observed that each step in this sequence relies on the completion of the prior step to function correctly.

When the first function readFile completes its task of reading the markdown content, it triggers the next callback to convert that content into HTML. This chain of callbacks continues ensuring all operations are executed in the specified order.

To effectively read and understand nested callback structures, you need to adopt a top-down approach.

Start with the outermost function and working your way inward. Each function call indicates a specific step in the overall process, and understanding this hierarchy will help you make sense of the code.

Get my free, weekly JavaScript tutorials

Want to improve your JavaScript fluency?

Every week, I send a new full-length JavaScript article to thousands of developers. Learn about asynchronous programming, closures, and best practices — as well as general tips for software engineers.

Join today, and level up your JavaScript every Sunday!

Thank you, Taha, for your amazing newsletter. I’m really benefiting from the valuable insights and tips you share.

- Remi Egwuda