0

I am working on an HTML5 mobile web app built on top of SalesForce. We make heavy use of JavaScript Remoting functions and I would like to know a way to wait for N (multiple) of these remote function calls to complete and then fire another event or function call. The use case we are trying to accomplish is as follows:

  1. Show jQuery Spinner/Loader with $.mobile.showPageLoadingMsg();
  2. Call Remote function 1, 2, 3, ..., N.
  3. Wait for Remote functions 1, 2, 3, ..., N to return but do NOT freeze the browser.
  4. Hide the jQuery Spinner/Loader with $.mobile.hidePageLoadingMsg();

    // Show a Loading Spinner while we wait for our remote requests to complete.
    $.mobile.showPageLoadingMsg();
    
    // Remote Request 1
    MyController.f1(
        function(result, event){
            if(event.type == 'exception') {
                alert('Error: ' + event.message);                     
            } else {
                // Do stuff here such as fill in a form with the results from f1
            }
        },
        {escape:true});
    
    // Remote Request 2
    MyController.f2(
        function(result, event){
            if(event.type == 'exception') {
                alert('Error: ' + event.message);                     
            } else {
                // Do stuff here such as fill in a form with the results from f2
            }
        },
        {escape:true});
    
    // ...
    
    // Remote Request N
    MyController.fN(
        function(result, event){
            if(event.type == 'exception') {
                alert('Error: ' + event.message);                     
            } else {
                // Do stuff here such as fill in a form with the results from fN
            }
        },
        {escape:true});
    
    // I want to wait until all of my requests to finish before I hide the loading spinner.
    wait_for_all(); 
    $.mobile.hidePageLoadingMsg();  
    
Chris Sansone
  • 231
  • 1
  • 4
  • 14
  • Can you keep a global int with a count of the number of async tasks running. Increment the counter every time you start a call (starting the spinner if the new count is 1), and decrement it when the call completes. Check for it to get decremented to zero and hide the spinner then. – DigitalGhost Aug 30 '12 at 22:52
  • Yes I thought of that but it doesnt seem to be like a very robust way to go. I was wondering if there was something I was missing. – Chris Sansone Aug 30 '12 at 22:54
  • As long as you decrement even when there is an error, it should work just fine, even when some of the tasks fail. – DigitalGhost Aug 30 '12 at 23:00
  • See also http://stackoverflow.com/questions/4368946/javascript-callback-for-multiple-ajax-calls. – Ryne Everett Jul 22 '14 at 14:50

2 Answers2

2

here is how I generally handle this case:

    function doManyAsync(doneCallback) {
        // Async worker counters
        var workers = 0;

        function tickWorkers() {
            workers--;
            if ( workers === 0 ) {
                if ( doneCallback )
                    doneCallback();
            }

        }

        // Queue off first one
        doAsyncFunc1(1,2,3, tickWorkers);
        workers++;

        // Queue off second
        doAsyncFunc2(function() {
            tickWorkers();
        });
        workers++;
        
    }

An async worker counter is used outside of the async function's scope to keep track of them. For each async function started, increment the counter. When they finish, the callback will decrement until they are all done and call the finished callback. This relies on the incrementing for every function. If there is a risk of the async functions failing then you can set a timeout to check every now and then for the worker count, and if it hits past a threshold and its not 0 then to fail or generate an error, as desired.

Here is how it might look with your code:

    function doManyAsync(doneCallback) {

        // Async worker counters
        var workers = 0;

        function tickWorkers() {
            workers--;
            if ( workers === 0 ) {
                if ( doneCallback )
                    doneCallback();
            }

        }

        // Remote Request 1
        MyController.f1(
            function(result, event){
                tickWorkers();
                if(event.type == 'exception') {
                    alert('Error: ' + event.message);                     
                } else {
                    // Do stuff here such as fill in a form with the results from f1
                }
            },
            {escape:true});
        workers++;

        // Remote Request 2
        MyController.f2(
            function(result, event){
                tickWorkers();
                if(event.type == 'exception') {
                    alert('Error: ' + event.message);                     
                } else {
                    // Do stuff here such as fill in a form with the results from f2
                }
            },
            {escape:true});
        workers++;

        // ...

        // Remote Request N
        MyController.fN(
            function(result, event){
                tickWorkers();
                if(event.type == 'exception') {
                    alert('Error: ' + event.message);                     
                } else {
                    // Do stuff here such as fill in a form with the results from fN
                }
            },
            {escape:true});
        workers++;

    }

    // You would then
    $.mobile.showPageLoadingMsg();
    doManyAsync(function() {
        $.mobile.hidePageLoadingMsg();
    })
Jasmine Hegman
  • 547
  • 7
  • 22
  • I was afraid I would get into some type of semaphore/counting situation. Thanks for the clean and relevant example. I have marked this is the answer. – Chris Sansone Aug 30 '12 at 23:45
  • A bit late responding, but regarding semaphores, this is where we are lucky that JavaScript always runs in a single thread. As long as we maintain our counters there should never be an issue. Webworkers bring in multi-threadedness, but are generally sandboxed AFAIK and can only communicate via messages, so semaphore issues are still marginalized. And you're welcome, glad I could help! :o) – Jasmine Hegman Sep 22 '12 at 22:05
0

Try using the jQuery when and done functions.

$.when(asyncFunc1, asyncFunc2).done(yourCallbackHere)

This way your callback function is called when all actions in the first clause are complete.

You can also replace done with then, and your callback will be called regardless of whether the deferred object(s) supplied are resolved or rejected.

This is a simple example of something you can try:

// Remote Request 1
MyController.f1(
  function(result, event){
    var dfr = $.Deferred(); // New deferred object.
    if(event.type == 'exception') {
        dfr.reject(); // Error, object should be rejected.
    } else {
        // Do stuff here such as fill in a form with the results from f1
        dfr.resolve(); // Success, object is resolved.
    }
    return dfr; // Return the deferred object.
});

Understandably, your f1 function may not return the same result as the internal callback, dfr. If it does, this is now plausible:

$when(MyController.f1).done(successCallback); // Callback only if resolved.
$when(MyController.f2).then(alwaysCallback); // Callback if resolved or rejected.
Aesthete
  • 18,622
  • 6
  • 36
  • 45
  • $when().done(); does not seem to work with JavaScript remoting. Im not sure why but I have tested this in the past with no luck. I believe JavaScript Remoting is rpc which is a bit different. – Chris Sansone Aug 30 '12 at 23:44
  • Yeah those remoting function probably don't implement the deferred object method. I made some changes that might interest you. – Aesthete Aug 31 '12 at 01:41