Asynchronous for cycle in JavaScript

I need a loop that waits for an async call before continuing. Something like:

for ( /* ... */ ) {

  someFunction(param1, praram2, function(result) {

    // Okay, for cycle could continue

  })

}

alert("For cycle ended");

How could I do this? Do you have any ideas?


Solution 1:

You can't mix synchronous and asynchronous in JavaScript if you block the script, you block the Browser.

You need to go the full event driven way here, luckily we can hide the ugly stuff away.

EDIT: Updated the code.

function asyncLoop(iterations, func, callback) {
    var index = 0;
    var done = false;
    var loop = {
        next: function() {
            if (done) {
                return;
            }

            if (index < iterations) {
                index++;
                func(loop);

            } else {
                done = true;
                callback();
            }
        },

        iteration: function() {
            return index - 1;
        },

        break: function() {
            done = true;
            callback();
        }
    };
    loop.next();
    return loop;
}

This will provide us an asynchronous loop, you can of course modify it even further to take for example a function to check the loop condition etc.

Now on to the test:

function someFunction(a, b, callback) {
    console.log('Hey doing some stuff!');
    callback();
}

asyncLoop(10, function(loop) {
    someFunction(1, 2, function(result) {

        // log the iteration
        console.log(loop.iteration());

        // Okay, for cycle could continue
        loop.next();
    })},
    function(){console.log('cycle ended')}
);

And the output:

Hey doing some stuff!
0
Hey doing some stuff!
1
Hey doing some stuff!
2
Hey doing some stuff!
3
Hey doing some stuff!
4
Hey doing some stuff!
5
Hey doing some stuff!
6
Hey doing some stuff!
7
Hey doing some stuff!
8
Hey doing some stuff!
9
cycle ended

Solution 2:

I simplified this:

FUNCTION:

var asyncLoop = function(o){
    var i=-1;

    var loop = function(){
        i++;
        if(i==o.length){o.callback(); return;}
        o.functionToLoop(loop, i);
    } 
    loop();//init
}

USAGE:

asyncLoop({
    length : 5,
    functionToLoop : function(loop, i){
        setTimeout(function(){
            document.write('Iteration ' + i + ' <br>');
            loop();
        },1000);
    },
    callback : function(){
        document.write('All done!');
    }    
});

EXAMPLE: http://jsfiddle.net/NXTv7/8/

Solution 3:

A cleaner alternative to what @Ivo has suggested would be an Asynchronous Method Queue, assuming that you only need to make one async call for the collection.

(See this post by Dustin Diaz for a more detailed explanation)

function Queue() {
  this._methods = [];
  this._response = null;
  this._flushed = false;
}

(function(Q){

  Q.add = function (fn) {
    if (this._flushed) fn(this._response);
    else this._methods.push(fn);
  }

  Q.flush = function (response) {
    if (this._flushed) return;
    this._response = response;
    while (this._methods[0]) {
      this._methods.shift()(response);
    }
    this._flushed = true;
  }

})(Queue.prototype);

You simply create a new instance of Queue, add the callbacks you need, and then flush the queue with the async response.

var queue = new Queue();

queue.add(function(results){
  for (var result in results) {
    // normal loop operation here
  }
});

someFunction(param1, param2, function(results) {
  queue.flush(results);
}

An added benefit of this pattern is that you can add multiple functions to the queue instead of just one.

If you have an object which contains iterator functions, you can add support for this queue behind the scenes and write code which looks synchronous, but isn't:

MyClass.each(function(result){ ... })

simply write each to put the anonymous function into the queue instead of executing it immediately, and then flush the queue when your async call is complete. This is a very simple and powerful design pattern.

P.S. If you're using jQuery, you already have an async method queue at your disposal called jQuery.Deferred.

Solution 4:

Also look at this splendid library caolan / async. Your for loop can easily be accomplished using mapSeries or series.

I could post some sample code if your example had more details in it.