One of the most lovable (yet frustrating) things about JavaScript and Node.js is the use of Asynchronous functions and the callback based system. Several times I've found myself wanting to halt for things to complete instead of sending off a bunch of processes all at once - an example being an array of URLs I want to ping and return the content of. So, I'm going to outline how to create synchronous asynchronous loops in Node.js. By this, I mean a loop which can loop X amount of times, which carries out asynchronous code, but waits for that code to complete before looping. So without more ado, let's get to it;

The first thing to note that this is an extremely basic version, supporting looping through typical objects such as arrays, and incrementing by 1. It can, however, by adapted to almost any situation should you choose to. So, to start with, let's just define a simple function, along with our parameters.

function syncLoop(iterations, process, exit) {
    // Body of the function
}

Just to talk about the params real quick;

iterations = the number of iterations to carry out
process    = the code/function we're running for every iteration
exit       = an optional callback to carry out once the loop has completed

So we have our function shell, now we need to instantiate an index, and a boolean to keep track of if we're done looping or not.

function syncLoop(iterations, process, exit) {
    var index = 0,
        done = false;
    // Body of function
}

Now we can keep track of where we are, and if we're finished or not (both important in loops!). The done boolean is going to be our way to check if we want to actually run again when called.

Right, this is where it gets slightly more complicated. We're going to create an object loop which is actually our loop object, and we're going to return it so we can control the loop from outside the function.

function syncLoop(iterations, process, exit) {
    var index = 0,
        done = false;
    var loop = {
        // Loop structure
    };
    return loop;
}

I'll come back to this is a little while. Ok, so we have our loop. What's important to have in a loop? Well, we need a way to access the index, move on in the loop, and a way to kill the loop - so let's implement these methods.

function syncLoop(iterations, process, exit) {
    var index = 0,
        done = false,
        shouldExit = false;
    var loop = {
        next: function(){
            if (done) {
                if (shouldExit && exit) {
                    return exit(); // Exit if we're done
                }
            }
            // If we're not finished
            if (index < iterations) {
                index++; // Increment our index
                process(loop); // Run our process, pass in the loop
            // Otherwise we're done
            } else {
                done = true; // Make sure we say we're done
                if(exit) exit(); // Call the callback on exit
            }
        },
        iteration: function() {
            return index - 1; // Return the loop number we're on
        },
        break: function(end) {
            done = true; // End the loop
            shouldExit = end; // Passing end as true means we still call the exit callback
        }
    };
    return loop;
}

Ok, to talk through this a little bit;

  1. The loop.next() is our loop controller. Our process should call loop.next() when it wishes to complete an iteration and move to the next. Basically, all loop.next() does is call our desired process again, unless we're finished, in which case it calls the final callback.

  2. The loop.iteration() function simply returns the index we're on. The first initialization means we'll always be one index ahead of the current iteration, so we return index - 1.

  3. The loop.break() just tells the loop to finish on the current iteration. You can pass an optional value to tell the loop to end as normal and call the exit() callback if desired. This is useful for loops which need to cleanup after themselves.

Right, we have a majority of the body here. So let's just kick it off, by calling loop.next() just before we return our loop.

function syncLoop(iterations, process, exit) {
    var index = 0,
        done = false,
        shouldExit = false;
    var loop = {
        next: function() {
            if(done) {
                if (shouldExit && exit) {
                    return exit(); // Exit if we're done
                }
            }
            // If we're not finished
            if (index < iterations) {
                index++; // Increment our index
                process(loop); // Run our process, pass in the loop
            // Otherwise we're done
            } else {
                done = true; // Make sure we say we're done
                if(exit) exit(); // Call the callback on exit
            }
        },
        iteration: function() {
            return index - 1; // Return the loop number we're on
        },
        break: function(end) {
            done = true; // End the loop
            shouldExit = end; // Passing end as true means we still call the exit callback
        }
    };
    loop.next();
    return loop;
}

And we're done! All that matters now is implementing our loop and running it, so let's see an example;

syncLoop(5, function(loop) {
    setTimeout(function() {
	    var i = loop.iteration();
	    console.log(i);
	    loop.next();
    }, 5000);
}, function() {
    console.log('done');
});

The above code simply prints out the current iteration we're with 5 seconds between each print, then logs done when complete. Go ahead and try it inside your browser console. Let's also check that our loop.break() works as expected.

var myLoop = syncLoop(5, function(loop) {
    setTimeout(function() {
	    var i = loop.iteration();
	    console.log(i);
	    loop.next();
    }, 5000);
}, function() {
    console.log('done');
});

setTimeout(myLoop.break, 10000);

In this scenario, we should see only the first two iterations printed before the loop is ended. Because we aren't passing a boolean value to myLoop.break() it's not logging out done. We could change this by using the following:

setTimeout(function() {
    myLoop.break(true);
}, 10000);

One important thing to note is that you can't kill a loop (cleanly) whilst in mid execution, it will wait until the current iteration is complete (which actually makes a lot of sense). It will just queue the break for the start of the next iteration, which is checked in loop.next().

Anyway, hopefully this was helpful - it's definitely been useful to me in my dealings with Javascript and Node.js. If you have any questions/comments/suggested changes just let me know!