The async library

(This is intended to follow Server-side AJAX with Node.js+Express.)

In using Node.js, one inevitably runs into difficult callback patterns. A number of simple libraries have been built to simplify the patterns; the most popular is probably async.

By the way, additional references on async include the following.

Official async documentation
A different tutorial

The waterfall function

One of the most common patterns involves a query that requires several verification steps before finally modifying a database. For example, if a student requests to enroll in a course, we might first want to confirm that the student's password is correct, then that the course actually exists in the database, then that the student isn't already enrolled in the course, before finally saving that the student is now enrolled. The overall structure of this would be the following (with A_sql being the password-verification query, B_sql being the course-validation query, C_sql being the already-subscribed query, and D_sql being the enrollment-addition query).

A// sequence of arbitrary code
db.all(A_sqlfunction (errrows) {
    if (err !== null) {
        H// error handling code
    } else {
        B// more arbitrary code, working with result from A_sql
        db.all(B_sqlfunction (errrows) {
            if (err !== null) {
                H// error handling code
            } else {
                C// more arbitrary code, working with result from B_sql
                db.all(C_sqlfunction (errrows) {
                    if (err !== null) {
                        H// error handling code
                    } else {
                        D// more arbitrary code, working with result from C_sql
                        db.run(D_sqlfunction (err) {
                            if (err !== null) {
                                H// error handling code
                            } else {
                                E// more arbitrary code
                            }
                        });
                    }
                });
            }
        });
    }
});

This deep nesting is burdensome. The waterfall function provided by async provides a way around this:

async.waterfall(
    [
        function (next) {
            A// sequence of arbitrary code
            db.all(A_sqlnext);
        },
        function (rowsnext) {
            B// more arbitrary code, working with result from A_sql
            db.all(B_sqlnext);
        },
        function (rowsnext) {
            C// more arbitrary code, working with result from B_sql
            db.all(C_sqlnext);
        },
        function (rowsnext) {
            D// more arbitrary code, working with result from C_sql
            db.run(D_sqlnext);
        },
        function (next) {
            E;
        }
    ],
    function (err) {
        if (err !== null) {
            H// error handling code
        }
    }
});

The waterfall takes a list of functions as its first argument. It calls the first function in the array, passing as a parameter to that function next, which is essentially a function that invokes the second function in the array. In calling the second function, any results passed into next are passed as arguments to the second function, as well as another next, which when invoked essentially invokes the third function in the array.

A complication to the waterfall is its error-handling.

The waterfall method assumes that the first argument to any callback is an “error” result. When next is finally called, the next function first checks whether the first argument is null or not. If it is non-null, then there is an error, and so waterfall stops going through the list, and instead it goes directly to the final function (the second argument to waterfall, after the list of functions that forms the first argument) passing that non-null error as the argument. However, if the error argument to next is null, only then does waterfall go on to calling the next function in the list.

Admittedly, this is all rather confusing when you hear it described; but in practice, it is a lot simpler than nesting callback within callback within callback.

(By the way, async is useful beyond just database queries: Throughout Node.js, it is entirely typical for any blocking call to follow the same format of taking a callback function where an error argument is passed first to the callback. Those can easily be used by waterfall.)

Functions handling arrays

Another very useful function in async is eachSeries, which is for doing the same action to each element in a list, where each action is blocking. To modify our example above, suppose that we want to allow a student to enroll in several courses simultaneously. It is quite a challenge to write SQL to perform an arbitrary number of INSERT queries. But eachSeries is well-suited to this.

async.eachSeries(courses,
    function (coursenext) {
        db.run(A_sqlnext);
    },
    function (err) {
        if (err !== null) {
            H// error handling code
        }
    });

The eachSeries function takes an array as the first argument and functions as the second and third arguments. It calls the second-argument function once for each element of the array, passing the array element as the first argument and a next function as the second argument. This function should process the array value as appropriate and then call next once it is done — probably within the callback to some other blocking function. If an error is passed into next, then no more array elements are processed, and the third-argument function is called. This third-argument function is also called when all array elements have been processed successfully; in that case, null is passed as the argument to this third-argument function.

The waterfall and eachSeries are just two of the most useful functions in the async module. There are many others, particularly several that work on different types of operations with arrays: map to create a new array of computed results based on each element of an existing array, some to find whether a condition applies to any element of an array, and so on.