1

I have this code as a starting point.

// $ = jQuery
// groupAdata and groupBdata are arrays 

function funcA(elem) {
    for (f = 0; f < groupAdata.length ; f++) {
        // this is an example on how this function calls other functions asynchronously. 
        elem.children('.partyA').each( function() {
            this.innerHTML = "been here" + groupAdata[f];
        });
    }
}

function funcB(elem) {
    // another function that fires more calls
    for (f = 0; f < groupAdata.length ; f++) {
        $.post(url, somedata, function(data) { 
            elem.children('.partyB').each( function() {
                this.innerHTML = "will be there" + groupBdata[f] + data;
            });
        }
    }
}

$(document).ready(function() {

    $('.groupA').each(function () {
        funcA(this);
    });

    $('.groupB').each(function (){
        funcB(this);
    });


});

function endofitall() {
    // call this after all instances of funcA and funcB are done.
}

When running endofitall(), I'd like to be sure that all calls of funcA and funcB are done.

I take that Promises and jQuery.Deferred() would be a good/preferred approach but was not able to map the answers I found to this specific scenario. (It is part of a templating tool that fires multiple dom manipulators func[AB] for multiple DOM elements.)

Volker
  • 497
  • 6
  • 15
  • 2
    why not call endofitall after the two each loops? – depperm Jun 25 '15 at 17:40
  • I found http://stackoverflow.com/a/4369592/696939 being the closest answer so far. Would that be an acceptable approach? – Volker Jun 25 '15 at 17:41
  • @depperm the funcA and funcB calls also spread of asynchronous calls. I added comments to clarify. – Volker Jun 25 '15 at 17:42
  • There is two ways to achieve this: setting yours ajax request as synchronous, or make them "nested" using callbacks. – Delgan Jun 25 '15 at 17:59
  • 1
    I can't see what's asynchronous in`funcA`. – ttzn Jun 25 '15 at 18:02
  • I agree, deferred is the right way to do it (actually the only clean and right one for me), it would be interesting to see your attempt to map the answers to see what was not working there. – Aurelio Jun 25 '15 at 18:15
  • possible duplicate of [Wait until all jQuery Ajax requests are done?](http://stackoverflow.com/q/3709597/1048572) or [jQuery Deferred - waiting for multiple AJAX requests to finish](http://stackoverflow.com/q/6538470/1048572) – Bergi Jun 25 '15 at 18:50
  • @Amine funcA is also asynchronous. E.g. if there are 100 `elem.children()`. the anonymous `function() { this.innerHTML = "been here" + groupAdata[f]; }` will be called 100 times. These calls will be synchronous, yet, they will run asynchronous after the call of `funcA`. – Volker Jun 26 '15 at 08:41
  • @Volker they most certainly won't. `$.each` isn't asynchronous. Unless we're using different definitions of "asynchronous". – ttzn Jun 26 '15 at 09:16

2 Answers2

1

Call endofitall() inside each iteration for funcA and funcB. Keep a counter and perform the actual work once the counter reaches the number signifying all the tasks are complete.

function funcA(elem) {
    for (f = 0; f < groupAdata.length ; f++) {
        // these calls are not async
        elem.children('.partyA').each( function() {
            this.innerHTML = "been here" + groupAdata[f];
        });
        endofitall();
    }
}

function funcB(elem) {
    // another function that fires more calls
    for (f = 0; f < groupBdata.length ; f++) {
        $.post(url, somedata, function(data) { 
            elem.children('.partyB').each( function() {
                this.innerHTML = "will be there" + groupBdata[f] + data;
            });
            endofitall();
        }
    }
}

$(document).ready(function() {

    $('.groupA').each(function () {
        funcA(this);
    });

    $('.groupB').each(function (){
        funcB(this);
    });


});

var counter=0;
function endofitall() {
    if(++counter==groupAdata.length + groupBdata.length){
       //do stuff
}
Fisch
  • 3,775
  • 1
  • 27
  • 38
  • You can just call it at the end of the function A if nothing is asynchronous there and check for groupBdata.length +1 – loli Jun 25 '15 at 18:13
  • 1
    An interesting solution, but I feel like it adds a bit of coupling. I kind of prefer Deferred/Promise-based solutions more. – Katana314 Jun 25 '15 at 19:14
1

You can use $.when().

Your goal should be to get to:

// call funcA, call funcB
$.when( funcA(), funcB() )
      // when everything is done go on with the callback
     .done(endofitall);

In the case of funcA (synchronous function there's no problem and it will work as is).

In the case of funcB (asynchronous) there are some things to consider. If it would be just one ajax call your code should be something like:

// This function returns a promise. 
// When it's fulfilled the callback (in your case '.done(endofitall)') 
// will be called. 
function funcB(somedata){
  return $.post(url, somedata);
}

As you are actually making more requests you have to return a resolved promise only when all calls have been fulfilled.

// an *Asynchronous* function, returns an array of promises
function funcB(elem, groupAdata) {
    var allCalls = [];
    // for each element in the array call the relative async 
    // function. While you're calling it push it to the array.
    groupAdata.forEach(data, function(data){
        allCalls.push( $.post(url, data) );
    });
    // allCalls is now an array of promises.
    // why .apply(undefined)? read here: https://stackoverflow.com/a/14352218/1446845
    return $.when.apply(undefined, allCalls);
}

At this point you can go for a flat and clear:

$.when( funcA(), funcB() ).done(endofitall);

As a rule of thumb: if you are making async requests try to always return a promise from them, this will help flatten out your code (will post some link later on if you want) and to leverage the power of callbacks.

The above code can surely be refactored further (also, I haven't used a lot of jQuery in the last few years, but the concept applies to any Js library or even when using no library at all) but I hope it will help as a starting point.

References:

Community
  • 1
  • 1
Aurelio
  • 24,702
  • 9
  • 60
  • 63