Waiting on multiple asynchronous calls to complete before continuing

41,234

Solution 1

You can use .ajaxStop() like this:

$(function() {
  $(document).ajaxStop(function() {
    $(this).unbind("ajaxStop"); //prevent running again when other calls finish
    LoadContact();
  });
  LoadCategories($('#Category'));
  LoadPositions($('#Position'));
  LoadDepartments($('#Department'));
});

This will run when all current requests are finished then unbind itself so it doesn't run if future ajax calls in the page execute. Also, make sure to put it before your ajax calls, so it gets bound early enough, it's more important with .ajaxStart(), but best practice to do it with both.

Solution 2

Expanding on Tom Lianza's answer, $.when() is now a much better way to accomplish this than using .ajaxStop().

The only caveat is that you need to be sure the asynchronous methods you need to wait on return a Deferred object. Luckily jQuery ajax calls already do this by default. So to implement the scenario from the question, the methods that need to be waited on would look something like this:

function LoadCategories(argument){
    var deferred = $.ajax({
       // ajax setup
    }).then(function(response){ 
       // optional callback to handle this response 
    });
    return deferred;
}

Then to call LoadContact() after all three ajax calls have returned and optionally executed their own individual callbacks:

// setting variables to emphasize that the functions must return deferred objects
var deferred1 = LoadCategories($('#Category'));
var deferred2 = LoadPositions($('#Position'));
var deferred3 = LoadDepartments($('#Department'));

$.when(deferred1, deferred2, deferred3).then(LoadContact);

Solution 3

If you're on Jquery 1.5 or later, I suspect the Deferred object is your best bet: http://api.jquery.com/category/deferred-object/

The helper method, when, is also quite nice: http://api.jquery.com/jQuery.when/

Share:
41,234
samiretas
Author by

samiretas

Consultant/Developer #SOreadytohelp

Updated on July 09, 2022

Comments

  • samiretas
    samiretas almost 2 years

    So, I have a page that loads and through jquery.get makes several requests to populate drop downs with their values.

    $(function() {
        LoadCategories($('#Category'));
        LoadPositions($('#Position'));
        LoadDepartments($('#Department'));
    
        LoadContact();
    };
    

    It then calls LoadContact(); Which does another call, and when it returns it populates all the fields on the form. The problem is that often, the dropdowns aren't all populated, and thus, it can't set them to the correct value.

    What I need to be able to do, is somehow have LoadContact only execute once the other methods are complete and callbacks done executing.

    But, I don't want to have to put a bunch of flags in the end of the drop down population callbacks, that I then check, and have to have a recursive setTimeout call checking, prior to calling LoadContact();

    Is there something in jQuery that allows me to say, "Execute this, when all of these are done."?

    More Info I am thinking something along these lines

    $().executeAfter(
        function () {   // When these are done
            LoadCategories($('#Category'));
            LoadPositions($('#Position'));
            LoadDepartments($('#Department'));
        },
        LoadContact // Do this
    );
    

    ...it would need to keep track of the ajax calls that happen during the execution of the methods, and when they are all complete, call LoadContact;

    If I knew how to intercept ajax that are being made in that function, I could probably write a jQuery extension to do this.

    My Solution

    ;(function($) {
        $.fn.executeAfter = function(methods, callback) {
    
            var stack = [];
    
            var trackAjaxSend = function(event, XMLHttpRequest, ajaxOptions) {
                var url = ajaxOptions.url;
    
                stack.push(url);
            }
    
            var trackAjaxComplete = function(event, XMLHttpRequest, ajaxOptions) {
                var url = ajaxOptions.url;
    
                var index = jQuery.inArray(url, stack);
    
                if (index >= 0) {
                    stack.splice(index, 1);
                }
    
                if (stack.length == 0) {
                    callback();
                    $this.unbind("ajaxComplete");
                }
            }
    
            var $this = $(this);
    
            $this.ajaxSend(trackAjaxSend)
            $this.ajaxComplete(trackAjaxComplete)
    
            methods();
            $this.unbind("ajaxSend");
        };
    })(jQuery);
    

    This binds to the ajaxSend event while the methods are being called and keeps a list of urls (need a better unique id though) that are called. It then unbinds from ajaxSend so only the requests we care about are tracked. It also binds to ajaxComplete and removes items from the stack as they return. When the stack reaches zero, it executes our callback, and unbinds the ajaxComplete event.

  • samiretas
    samiretas almost 14 years
    That's good, and seems to work in the scenario I have, but, let's add a caveat, there's a fourth async call that takes a long time, that I don't want to wait for as LoadContact doesn't require it to be complete. Is there anyway to say that ONLY these three things need to be complete?
  • samiretas
    samiretas almost 14 years
    This is what I'm trying to avoid. The callbacks should know nothing, nor care about what runs after them, or relies upon them, or if other callbacks are executing, or need to be. That's just introducing very convoluted logic.
  • Nikita Rybak
    Nikita Rybak almost 14 years
    I see one easy solution: pass helper function as parameter to LoadCategories, etc ('executeMeAfterCallback') with your logic. Pretty much like ajaxStop, but gives you more control on which requests you want to involve.
  • Nikita Rybak
    Nikita Rybak almost 14 years
    BTW, is there a reason you have three different methods for loading lists and not one (with necessary parameters, like url)? Just curious.
  • samiretas
    samiretas almost 14 years
    they all call a common function with the proper url and target element. The reason for three functions that wrap that common method, is to keep the code clean, what if I need to reload the departments for some reason later? Or load them in another control as well, I want the implementation of how that is done hidden, and thus, just call LoadDepartments(targetElement);
  • Nick Craver
    Nick Craver almost 14 years
    @Chad - Nope, if you want to pick and choose...going to have to put a counter in yourself, no way for a global handler to know which ones you care about.
  • Nikita Rybak
    Nikita Rybak almost 14 years
    Yeah, that's a good point. Still, executeMeAfterCallback should do it. Later you can always call LoadDepartments without second parameter.
  • samiretas
    samiretas almost 14 years
    I've added a solution that appears to do what I require. Your answer definitely pointed me in the correct direction!
  • samiretas
    samiretas almost 14 years
    gave you the answer since it was correct until I added the caveat.
  • samiretas
    samiretas almost 12 years
    Agreed, that's what I use now
  • Gone Coding
    Gone Coding about 11 years
    +1 for introducing me to ajaxStop. I now have recusive async multiple loads working. Cheers
  • Markus Schober
    Markus Schober over 9 years
    Which part of deferred solves this problem? There are a lot of options, it's hard to tell how to address this.