0

I'm looking for advice on extending a previous accepted answer regarding chained ajax requests.

The following asynchronous-chain solution was proposed to sequence 3 ajax requests:

var step_3 = function() {
    c.finish();
};

var step_2 = function(c, b) {
    ajax(c(b.somedata), step_3);
};

var step_1 = function(b, a) {
  ajax(b(a.somedata), step_2);
};

ajax(a, step_1);

This is great for a small pre-determined number of chained ajax functions but does not scale well to the case of a variable number of such functions.

I've tried doing the following but seem to run into scoping issues due my admitted lack of javascript expertise:

var asynch      = function (options, fNext) {// do something asynchronously} 
var chain       = {f:[]} // chain of asynchronous functions
var args        = function(n){ //return arguments to feed n'th asynch function }
for (n=0;n<N;n++) 
{
    var a       = args(n);
    var ftmp    = n==N-1? function(){} : chain.f[n+1]
    chain.f[n]  = function () {asynch(a, ftmp)}
}
chain.f[0]()  // initiate asynchronous chain
Cœur
  • 37,241
  • 25
  • 195
  • 267
Code True
  • 636
  • 7
  • 18
  • What is it you're trying to do here? Are you effecting the document at all? The DOM? If no, if you're using this to calculate some data, or to serve up some data, then you could avoid using AJAX and try Web Workers. – Mike Dec 19 '13 at 21:19
  • 1
    You might want to look into the excellent async library: https://github.com/caolan/async and jQuery’s promise interface: http://api.jquery.com/category/deferred-object/ – David Hellsing Dec 19 '13 at 21:21
  • Looks like you have a scope problem here: `chain.f[n] = function () {asynch(a, ftmp)}` `a` and `ftmp` will always equal the value of the last iteration through the for loop. classic for loop scoping issue, solved by giving each iteration of the for loop a private scope. – Kevin B Dec 19 '13 at 21:21
  • @Mike, My n'th asynch function is making an ajax request to a server to fetch data which I subsequently need to order based on n. I'm unfamiliar with Web Workers, but will look into it. Thnx for the comment. – Code True Dec 19 '13 at 21:26
  • @David, I've looked into jQuery deferred-objects a bit but it seems like I'd have to add more code complexity than I desire to make it work. I will check out the async library though, thnx for pointer. – Code True Dec 19 '13 at 21:30
  • @Kevin, yes I agree, but its not obvious to me how to give each for loop iteration its own private scope here (like i said, I'm admittedly not a javascript expert). Any suggestion on how to do this? Thanks for the comment. – Code True Dec 19 '13 at 21:34
  • I'm working on an answer, but there's more problems than that. You're assigning chain.f[n+1] to ftmp before chain.f[n+1] is defined. little bit of a chicken or the egg situation goin on there. – Kevin B Dec 19 '13 at 21:35
  • possible duplicate of [Javascript closure inside loops - simple practical example](http://stackoverflow.com/questions/750486/javascript-closure-inside-loops-simple-practical-example) – Bergi Dec 19 '13 at 21:42
  • @jb1 adding multiple async ajax requests is really simple in jQuery using deferred: `$.when($.get(u1), $.get(u2), $.get(u3)).then(doSomething);` http://api.jquery.com/jQuery.when/ – David Hellsing Dec 19 '13 at 21:44
  • @Kevin, I thought of using eval('chain.f[n+1]') to try and avoid chicken and egg but I like to reserve 'eval' based solutions for absolute last alternative. – Code True Dec 19 '13 at 21:46

2 Answers2

0

Asynchronous loops are a pain in the ass. As a rule of thumb, if you want to do them by hand you need to rewrite the for loop as a recursive function.

function sequence_synchronous(steps){
  for(var i=0; i<steps.length; i++){
    steps[i]();
  }
  return;
}


function sequence_async(steps, callback){
  var i = 0;
  var next_step = function(){
    if(i >= steps.length){
       callback();
    }else{
       steps[i](function(){
          i++;
          next_step();
       });
    }
  }
  next_step();
}

Note that this doesn't attempt to build a big chain of callbacks before calling the first one - all we did was convert the traditional for loop into continuation passing style.

I would highly recommend looking for a library to do this for you though.

David Hellsing
  • 106,495
  • 44
  • 176
  • 212
hugomg
  • 68,213
  • 24
  • 160
  • 246
0

What you have is a very common scoping issue with for loops. Each iteration of the for loop is using the same local scope as the parent function, meaning anything that happens asynchronously will end up accessing the last value of the loop rather than the value at the time it was defined. See this fiddle as an example: http://jsfiddle.net/GAG6Q/ Instead of asynch getting called 9 times, it gets called once with a value of 9. You can fix it by simply providing a private scope for the inside of the loop. You'll also want to wrap chain.f[n+1] in a function so that you don't try to assign undefined to ftmp.

http://jsfiddle.net/GAG6Q/1/

var N = 10;
var asynch      = function (options, fNext) {
    console.log(options);
    setTimeout(fNext,500);
}// do something asynchronously} 
var chain       = {f:[]} // chain of asynchronous functions
var args        = function(n){return n;} //return arguments to feed n'th asynch function }
for (n=0;n<N;n++) 
{
    (function(n){
        var a       = args(n);
        var ftmp    = n==N-1? function(){} : function(){chain.f[n+1]();};
        chain.f[n]  = function () {asynch(a, ftmp)}
    })(n);
}
chain.f[0]()  // initiate asynchronous chain
Kevin B
  • 94,570
  • 16
  • 163
  • 180