2

I am polling data asynchronously, from a number of sources, in a round-robin approach and wish to repeat the poll when all of these polls have finished. I am trying to use the jQuery Deferred object together with "always" to repeat my polling as shown in the code below:

function makeAjaxCall(region) {
    var params = {
        'action': POLLER.action,
        },
        url = POLLER.region_to_url[region];

    return $.ajax({
        dataType: "json",
        url: url,
        data: params,
        success: (function(region) {
                      return function(result, status) { 
                          handleAjaxResult(result, status, region); 
                      };
                 })(region),
        error: (function(region) {
                    return function(jqXHR, textStatus, errorThrown) { 
                        handleAjaxError(jqXHR, textStatus, errorThrown, region); 
                    };
                })(region)
    });
}

function nextPoll() {
    if(!polling) {
        return;
    }

    var requests = [];

    $.each(POLLER.regions, function(i, region) {
        requests.push(makeAjaxCall(region));
    });

    $.when.apply($, requests)
        .always(function() {
            log("deferred.always ", this)
            updateSummary();
            var delay = POLLER.POLLER_INTERVAL_MS;
            if (delay != 0) {
                pollerTimeout = setTimeout(nextPoll, delay);
            }
        }).fail(function() {
            log("fail for ",this)
        });
}

My problem is that when one of my polls fails, the "always" block is called. I may be assuming incorrectly that "always" should be called after all requests have completed or failed. It is my intention to have it behave this way, so any tips on a different, perhaps simpler, approach would be great.

skila
  • 69
  • 5
  • I created a [jsFiddle](http://jsfiddle.net/gh9MA/) which recreates what I think you're expecting, I haven't really changed the core of your code and it seems to behave as you expect - the always seems to log out after all three ajax requests. – Ross Sep 03 '13 at 12:29
  • If I edit the [jsFiddle](http://jsfiddle.net/gh9MA/1/) to fail on the 2nd count, you should see what I mean in the console. – skila Sep 03 '13 at 13:10
  • Ah, thank you. I see what you mean. Looking at a comment in [another post](http://stackoverflow.com/questions/5573165/raising-jquery-deferred-then-once-all-deferred-objects-have-been-resolved) it seems like that is 'working as intended' behaviour for when - it resolves immediately when one of the objects is rejected/fails. – Ross Sep 03 '13 at 13:24

2 Answers2

0

Thanks @Ross for your help. I managed to rework my code to remove use of the Deferred object and just keep track of how many ajax requests had returned, so in the success and error handlers of the ajax call, I also always call the same function which tracks the number of polls.

function makeAjaxCall(region) {
    var params = {
        'action': POLLER.action,
        },
        url = POLLER.region_to_url[region];

    $.extend(params, POLLER.filters);

    return $.ajax({
        dataType: "json",
        url: url,
        data: params,
        success: (function(region) {
            return function(result, status) { handleAjaxResult(result, status, region); };
        })(region),
        error: (function(region) {
            return function(jqXHR, textStatus, errorThrown) { handleAjaxError(jqXHR, textStatus, errorThrown, region); };
        })(region)
    });
}

function handleAjaxResult(result, status, region) {
    POLLER.data[region] = results.data;
    afterAjaxResult();
}

function handleAjaxError(jqXHR, textStatus, errorThrown, region) {
    POLLER.ajax_errors[region].count++;
    afterAjaxResult();
}

function nextPoll() {
    if(!polling) {
        return;
    }

    POLLER.round_robin = 0;

    $.each(POLLER.regions, function(i, region) {
        makeAjaxCall(region)
    });
}

function afterAjaxResult() {
    POLLER.round_robin++;
    if(POLLER.regions.length === POLLER.round_robin) {
        updateSummary();
        var delay = POLLER.POLLER_INTERVAL_MS;
        if (delay != 0) {
            pollerTimeout = setTimeout(nextPoll, delay);
        }
    }
}

See this jsFiddle for an example.

skila
  • 69
  • 5
0

I wrote an extension to $.when that does exactly this.

This extension treats all successes and failures as progress events. After all the promises have completed, the global promise is resolved if there were no errors. Otherwise the global promise is rejected.

$.whenAll - https://gist.github.com/4341799 (tests)

Sample usage:

$.whenAll($.getJSON('foo'), $.getJSON('bar'))
  .then(
    doneCallback
    ,failcallback
    // progress callback
    // the only problem is $.ajax.done/fail states call their callbacks 
    // with params in different locations (except for state)
    ,function(data, state, jqXhr) {
      if (state == 'success') {
        // do happy stuff
      }
      else { // error (fail)
        // `data` is actually the jqXhr object for failed requests
        // `jqXhr` is the text of the error "Not Found" in this example
      }
    }
  )
;
fearphage
  • 16,808
  • 1
  • 27
  • 33