Startec
Startec

Reputation: 13206

My jquery $.Deferred is not preventing another $.ajax call from happening until resolved

I am using long polling to retrieve a large json feed with image URLs. I am the preloading these urls in the success portion of my $.ajax request and want to call a function to do something with these images once they have totally loaded. I am trying to do this by using the $.Deferred object in the complete portion of my $.ajax request, and resolving the $.Deferred at the end of my image preloading loop.

However, as I monitor the console, I can see that the $.ajax calls continue to be made while the images are preloading. the $.Deferred is not resolved until the end of the preloading loop, so I do not understand why this would happen. I have a limited understand of the $.Deferred but am trying to learn more so any advice on why this is happening and how to fix this would be great. The understanding I got was that they were a way to prevent a function from being called until they are reserved...

As a bonus here, other than ugly code is there an issue with making multiple $.ajax requests and doing the same thing with the data that is returned? Thanks

$(function(){
    (function poll(){
        var streamUrl = 'http://demofeed';
        var def = $.Deferred();
        console.log('about to get');
        setTimeout(function(){
            $.ajax({
                url: streamUrl,
                dataType: 'json',
                success: function(data) {
                    createArray(data, def);
                },
                complete: function () {
                    $.when(def).done(function() {
                        //Long-poll again
                        poll();
                    });
                },
                error: function(){
                    setTimeout(function(){
                        console.log('There was an error with the XHR Request');
                        poll();
                    }, 3000);
                }
            });
        }, 7000);
    })();
});


function createArray(data, defer){
    console.log('creating array');
    var entries = data['entries'];
    for (var k = 0, j = entries.length; k < j; k++) {
        console.log('there is new media');
        if (entries[k].img) {
            var img = new Image();
            img.onload = (function(entry) {
                return function() {
                    validArray.push(entry);
                    console.log('New Media Loaded');
                }
            })(entries[k]);
            img.onerror = function(){
                console.log('error: bad image source');
            };
            img.src = entries[k].url;
        } else {
            console.log('Not an image');
        }
    }
    defer.resolve(); //Here is where I resolve the deferred object, once for loop is done (all images have been loaded)
}

Upvotes: 1

Views: 678

Answers (1)

Benjamin Gruenbaum
Benjamin Gruenbaum

Reputation: 276306

$.when waits for promises.

You are passing it a deferred object instead. It in turn gets converted to a promise by wrapping it, so waiting for it yields immediately with it as the value. So the .when executes immediately. You can instead use def.promise() to get a promise for it.

That said, I'd probably do a more promisified API overall if I were you.

var delay = function(ms){
   var def = $.Deferred();
   setTimeout(function(){ def.resolve(); },ms);
   return def.promise();
}

Which would let me do:

function poll(){
    console.log('about to get');
    return delay(7000).then(function(){
        return $.get('http://demofeed');
    }).then(function(){ // success case
         return createArray(data); // we'll get to it later
    }), function(){ // handle error
          console.log('There was an error with the XHR Request');
          return delay(3000); // wait 3 ms
    }).then(poll); // long poll
}

Look how clear it is and hot it only has two levels of indentation, this is because promises chain. If your code was sequential, this is roughly:

  function poll(){
      sleep(7000);
      try{
          var data = $.get('http://demofeed'); // assume this is sync
          arr = createArray(data);
      } catch(e) {
          console.log("There was an error with the XHR Request");
          sleep(3000); 
      }
      poll(); /// call again
  }

As for the load images function, consider using $.when.apply on it to have $.when wait for multiple values:

function createImages(data){ // what does this return?
    var promises = data.entries.map(function(entry){
        var img = new Image();
        var d = $.Deferred();
        img.onload = function(entry){ d.resolve(entry); };
        img.onerror = function(){ console.log("Bad Image usage"); }
        return d.promise();
    });
    // this will actually _wait_ for the images now.
    return $.when.apply($, promises); 
}

Last note, if I were you I'd avoid jQuery promises and use a more capable library like Bluebird.

Upvotes: 3

Related Questions