Unnest Callbacks
Unnest callbacks
Akbar S. Ahmed | Apr 30, 2014

Improve Node.js performance

Unnesting callbacks in JavaScript involves changing anonymous functions to named functions. It also requires that we unnest the callback function from the original function’s list of arguments by passing the callback’s name as the argument (vs. an inline function).

The following pseudo code highlights unnesting:

// Nested, anonymous callback function
someFun(1, 2, function() {
    // I am now in the anonymous callback function
    ...
});

// Unnested named callback function
someFun(1, 2, namedCallback);

function namedCallback() {
    // I am now in the named, unnested callback function
    ...
}

Unnesting callbacks in JavaScript improves the readabilty of our code and provides a nice performance improvement. This post highlights a few examples using sequential function calls. However, for more complex scenarios you will likely want to use a dedicated library such as async, which we’ll discuss in a later post.

Example code

All examples used in this post are available on GitHub. You can clone and setup the examples by running the following commands:

git clone git@github.com:akbarahmed/unnest-callbacks-in-javascript.git

cd unnest-callbacks-in-javascript

npm install

Avoiding callback hell

The examples in this post will help you avoid callback hell by providing a code structure that is both simple and shallow. Also, unnested callbacks allow you to read your code top to bottom in the order of execution.

Let’s get started.

Nested callbacks

The following example does not nest too deeply, however you can see that the code would shift even further to the right if we had to nest a few more levels. Taken too far and this code becomes increasingly difficult to read. Also, as we’ll see below, using named functions also improves the error messages displayed by Node.

#!/usr/bin/env node

var fs = require('fs');

fs.readFile('./file1.txt', function (err, file1Contents) {
    if (err) throw err;
    fs.readFile('./file2.txt', function (err, file2Contents) {
        if (err) throw err;

        var file3Contents = [file1Contents, file2Contents].join('');

        fs.writeFile('file3.txt', file3Contents, function (err) {
            if (err) throw err;
            console.log('Concatenated file1.txt and file2.txt into file3.txt.');
        });
    });
});

You can run the nested callback example with the following command:

node concat-files-nested.js

Nested callbacks with an error

Run the nested callback example with errors to view the error message displayed by Node when we use anonymous functions.

node concat-files-nested-with-error.js
/repos/unnest-callbacks-in-javascript/concat-files-nested-with-error.js:13
            if (errWrong) throw err;
                ^
ReferenceError: errWrong is not defined
    at /repos/unnest-callbacks-in-javascript/concat-files-nested-with-error.js:13:8
    at Object.oncomplete (fs.js:107:15)

The important thing to notice in the error message above is that the line number where the error occurred 13 is displayed, however the function in which the error occurred is not displayed. In a large code base, the function name is a useful pointer when debugging, yet it’s not displayed above.

Importantly, we’ll see how using named functions results in improved error messages below.

Unnested callbacks

Unnested callbacks results in shallow code that’s easier to read and that runs faster.

The first example we’ll view shows the use of unnested callbacks where values are passed via properties. However, the GitHub repo contains other examples including passing variables via file globals.

node concat-files-unnested-pass-via-properties.js

This code gives the exact same results as our nested callbacks code. However, the source is easier to read as the flow from the initial readFile() to readFile1(), then readFile2() and finally writeFile3() executes sequentially from top to bottom.

#!/usr/bin/env node

// In this example we've unnested the callbacks. Information is passed by
// setting file1Contents and file2Contents as properties of the writeFile3()
// function. file3Contents, which is what we write to file3.txt is kept as a
// local variable in the readFile2() function.

var fs = require('fs');

fs.readFile('./file1.txt', readFile1);

function readFile1(err, data) {
    if (err) throw err;

    writeFile3.file1Contents = data;
    fs.readFile('./file2.txt', readFile2);
}

function readFile2(err, data) {
    if (err) throw err;

    writeFile3.file2Contents = data;
    var file3Contents = [
        writeFile3.file1Contents,
        writeFile3.file2Contents
    ].join('');
    fs.writeFile('file3.txt', file3Contents, writeFile3);
}

function writeFile3(err) {
    if (err) throw err;
    console.log('Concatenated file1.txt and file2.txt into file3.txt.');
}

Unnested callbacks with an error

Run the following script to view the error message displayed by Node when named callback functions are used vs. the nested, anonymous callback functions used in the Nested callback with an error section above.

node concat-files-unnested-pass-via-properties-with-error.js

The error message when unnested, named callback functions are used is displayed below.

/repos/unnest-callbacks-in-javascript/concat-files-unnested-pass-via-properties-with-error.js:31
    if (errWrong) throw err;
        ^
ReferenceError: errWrong is not defined
    at writeFile3 (/repos/unnest-callbacks-in-javascript/concat-files-unnested-pass-via-properties-with-error.js:31:6)
    at Object.oncomplete (fs.js:107:15)

The key difference is that Node now reports the exact function writeFile3 where the error occurred.

What about async

The async module is absolutely excellent and should be a part of your toolkit. However, using named, unnested callback functions helps to improve your codes readability and performance.

However, when creating a sequential flow it’s often easier to organize your code as shown above and eliminate the need for abstraction.

While unnesting callbacks is not necessary when nesting is only a few levels deep, it’s good practice to standardize on named, unnested callbacks. This helps your team by having a standard that always works well. The alternative is to add complexity to your coding standards that document when nesting is appropriate and when it it not appropriate.

I hope you found this information useful.




Subscribe to our newsletter

Contact Information

ABOUT EXPONENTIAL.IO

We specialize in helping professional developers, like you, expand your skill set. Our courses are focused on enabling you to learn everything necessary to use a new technology in a live, production application.

LOCATION

All courses are made with love in
Palo Alto, CA.

Subscribe to our newsletter