Nado11
Nado11

Reputation: 105

chaining jquery .when().then() in a loop with fixed end of chain call

The closest answer I could find was this https://stackoverflow.com/a/17216555/2834734

The most common use for .then is chaining ajax requests:

$.ajax({...}).then(function(){ return $.ajax({...}); }).then(function(){ return $.ajax({...}); }).then(function(){ return $.ajax({...}); }).then(function(){ return $.ajax({...}); });

this can easily be done in a loop

However it's the looping procedure I'm having difficulty with plus I have some unusual circumstances.

A brief explanation is, I have an array of requests that I need to loop through, some will invoke an ajax load and others will not. I need them to run consecutively but also run a specific function call at then end.

Here is a simple(I hope) sample of my situation:

      // Here is my flow of logic
       var thingsToDo = new tasks(); // Initiate the constructor, explained below

        // Loop through the requests array and process them consecutively
       for (var i in thingsToDo.requests) {
         $.when(thingsToDo.jqxhr).then(function() {
           thingsToDo.requests[i].fn();
         })
       }
        // Run my final function at the end of the chain.
       $.when(thingsToDo.jqxhr).then(function() {
         runOnceAllComplete();
       });

This is the constructor class the above is based on.

        // Constructor
       function tasks() {
         _tasks_ = this; // automatic global var 
         this.jqxhr = undefined; // Var to monitor ajax calls
         this.requests = [ // list of tasks to run.
           {
             url: 'file1.php',
             fn: function() {
               _tasks_.load(this.url);
               console.log('file1 loaded');
             }
           }, {
             url: 'file2.php',
             fn: function() {
               _tasks_.load(this.url);
               console.log('file2 loaded');
             }
           }, {
             noUrl: true, // Note there is no file to load here
             fn: function() {
               console.log('no file here to load, but process something else');
               $('body').css("background-color", "blue");
             }
           }, {
             url: 'file3.php',
             fn: function() {
               _tasks_.load(this.url);
               console.log('file1 loaded');
             }
           },
         ];
         this.load = function(file) { // This runs the ajax call and resets this.jqxhr             
           this.jqxhr = $.get(file);
         }
       }

       function runOnceAllComplete() {
         alert('hooray!, we finished');
       }

The tricky part I have is the requests are created dynamically so there can be 1-n many requests to perform, which is why I chose to loop, and they must be performed in that order.

As mentioned some requests will invoke an ajax call and others may not, this doesn't seem to break $.when().then(), but the problem is the loop continues before the promise is resolved and my final function happens before the final request. Still trying to get my head around promises, the first time I've used them.

Upvotes: 0

Views: 1771

Answers (1)

guest271314
guest271314

Reputation: 1

Try including return statement at fn , this.load ; adding .promise() chained to $("body") at fn to return a jQuery promise object ; using Function.prototype.apply() , $.map() at $.when()

fn: function() {
  // added `return`
  return _tasks_.load(this.url);               
}

this.load = function(file) { 
  this.jqxhr = $.get(file);
  // added `return`
  return this.jqxhr
}

fn: function() {
  console.log('no file here to load, but process something else');
  // added `return` , `.promise()`
  return $('body').css("background-color", "blue").promise();
}

$.when.apply($, $.map(thingsToDo.requests, function(task) {
  return task.fn()
})).then(runOnceAllComplete)

See also Pass in an array of Deferreds to $.when() , What does $.when.apply($, someArray) do?


however I'm encountering a problem, using the .map() it doesn't wait for each request to complete before processing the next one. I need each one to complete before moving to the next.

Try using .queue() , which will calls functions in queue sequentially, and only when next is called at current function

$(thingsToDo).queue("tasks", $.map(thingsToDo.requests, function(task) {
  return function(next) {
    // call next function in `"tasks"` queue
    // when current function completes using `.then(next)`
    return task.fn().then(next)
  }
})).dequeue("tasks").promise("tasks").then(runOnceAllComplete)

See .queue() , .promise() , Execute function queue in javascript

Upvotes: 1

Related Questions