views:

299

answers:

7

Okay, so I appreciate that Javascript is not C# or PHP, but I keep coming back to an issue in Javascript - not with JS itself but my use of it.

I have a function:

function updateStatuses(){

showLoader() //show the 'loader.gif' in the UI

updateStatus('cron1'); //performs an ajax request to get the status of something
updateStatus('cron2');
updateStatus('cron3');
updateStatus('cronEmail');
updateStatus('cronHourly');
updateStatus('cronDaily');

hideLoader(); //hide the 'loader.gif' in the UI

}

Thing is, owing to Javascript's burning desire to jump ahead in the code, the loader never appears because the 'hideLoader' function runs straight after.

How can I fix this? Or in other words, how can I make a javascript function execute in the order I write it on the page...

+9  A: 

The problem occurs because AJAX is in its nature asynchronus. This means that the updateStatus() calls are indeed executed in order but returns immediatly and the JS interpreter reaches hideLoader() before any data is retreived from the AJAX requests.

You should perform the hideLoader() on an event where the AJAX calls are finished.

Dr. Sbaitso
Exactly, JS doesn't wait for the updateStatus jobs to finish before starts the next jobs. If cron1 has to finish before cron2 starts, etc. You will need to set each updateStatus to run on Success of the previous call.
MindStalker
A: 

This has nothing to do with the execution order of the code.

The reason that the loader image never shows, is that the UI doesn't update while your function is running. If you do changes in the UI, they don't appear until you exit the function and return control to the browser.

You can use a timeout after setting the image, giving the browser a chance to update the UI before starting rest of the code:

function updateStatuses(){

  showLoader() //show the 'loader.gif' in the UI

  // start a timeout that will start the rest of the code after the UI updates
  window.setTimeout(function(){
    updateStatus('cron1'); //performs an ajax request to get the status of something
    updateStatus('cron2');
    updateStatus('cron3');
    updateStatus('cronEmail');
    updateStatus('cronHourly');
    updateStatus('cronDaily');

    hideLoader(); //hide the 'loader.gif' in the UI
  },0);
}

There is another factor that also can make your code appear to execute out of order. If your AJAX requests are asynchronous, the function won't wait for the responses. The function that takes care of the response will run when the browser recieves the response. If you want to hide the loader image after the response has been recieved, you would have to do that when the last response handler fuction runs. As the responses doesn't have to arrive in the order that you sent the requests, you would need to count how many responses you got to know when the last one comes.

Guffa
Thanks to Guffa and Dr. Sbaitso, I understand a lot more know what's going on under the hood, I'm going to give some of your suggestions a try and see what I can make work!
Ed
+1  A: 

Install Firebug, then add a line like this to each of showLoader, updateStatus and hideLoader:

Console.log("event logged");

You'll see listed in the console window the calls to your function, and they will be in order. The question, is what does your "updateStatus" method do?

Presumably it starts a background task, then returns, so you will reach the call to hideLoader before any of the background tasks finish. Your Ajax library probably has an "OnComplete" or "OnFinished" callback - call the following updateStatus from there.

Douglas
the updateStatus method fires off a jquery ajax call. The reason I can't hide the loader in the 'Onsuccess' callback is because I need to wait for multiple calls to complete. But using the 'counter' approach given below I've got a workaround
Ed
+2  A: 

You need to think of JavaScript as event based rather than procedural if you're doing AJAX programming. You have to wait until the first call completes before executing the second. The way to do that is to bind the second call to a callback that fires when the first is finished. Without knowing more about the inner workings of your AJAX library (hopefully you're using a library) I can't tell you how to do this, but it will probably look something like this:

showLoader();

  updateStatus('cron1', function() {
    updateStatus('cron2', function() {
      updateStatus('cron3', function() {
        updateStatus('cronEmail', function() {
          updateStatus('cronHourly', function() {
            updateStatus('cronDaily', funciton() { hideLoader(); })
          })
        })
      })
    })
  })
});

The idea is, updateStatus takes its normal argument, plus a callback function to execute when it's finished. It's a reasonably common pattern to pass a function to run onComplete into a function which provides such a hook.

Update

If you're using jQuery, you can read up on $.ajax() here: http://api.jquery.com/jQuery.ajax/

Your code probably looks something like this:

function updateStatus(arg) {
  // processing

  $.ajax({
     data : /* something */,
     url  : /* something */
  });

  // processing
}

You can modify your functions to take a callback as their second parameter with something like this:

function updateStatus(arg, onComplete) {
  $.ajax({
    data : /* something */,
    url  : /* something */,
    complete : onComplete // called when AJAX transaction finishes
  });

}

meagar
Hi Meagar, I'm using jquery - what would best way of implementing this be?
Ed
+3  A: 

We have something similar in one of our projects, and we solved it by using a counter. If you increase the counter for each call to updateStatus and decrease it in the AJAX request's response function (depends on the AJAX JavaScript library you're using.)

Once the counter reaches zero, all AJAX requests are completed and you can call hideLoader().

Here's a sample:

var loadCounter = 0;

function updateStatuses(){
    updateStatus('cron1'); //performs an ajax request to get the status of something
    updateStatus('cron2');
    updateStatus('cron3');    
    updateStatus('cronEmail');
    updateStatus('cronHourly');
    updateStatus('cronDaily');
}

function updateStatus(what) {
    loadCounter++;

    //perform your AJAX call and set the response method to updateStatusCompleted()
}

function updateStatusCompleted() {
    loadCounter--;
    if (loadCounter <= 0)
        hideLoader(); //hide the 'loader.gif' in the UI
}
Prutswonder
Hi Prutswonder, I just tried this and it works! I like the solution
Ed
Ed, if you like one of these solutions, could you mark your choice of the best solution by clicking the checkbox next to that person's answer?
Tim Snowhite
+1 Probably better than my solution for such a deeply nested series of callbacks
meagar
You might want the `loadCounter < 0` case to cause an error, since if that ever happens, it's a bug.
Matthew Crumley
A: 

move the updateStatus calls to another function. make a call setTimeout with the new function as a target.

if your ajax requests are asynchronous, you should have something to track which ones have completed. each callback method can set a "completed" flag somewhere for itself, and check to see if it's the last one to do so. if it is, then have it call hideLoader.

lincolnk
A: 

As others have pointed out, you don't want to do a synchronous operation. Embrace Async, that's what the A in AJAX stands for.

I would just like to mention an excellent analogy on sync v/s async. You can read the entire post on the GWT forum, I am just including the relevant analogies.

Imagine if you will ...

You are sitting on the couch watching TV, and knowing that you are out of beer, you ask your spouse to please run down to the liquor store and fetch you some. As soon as you see your spouse walk out the front door, you get up off the couch and trundle into the kitchen and open the fridge. To your surprise, there is no beer!

Well of course there is no beer, your spouse is still on the trip to the liquor store. You've gotta wait until [s]he returns before you can expect to have a beer.

But, you say you want it synchronous? Imagine again ...

... spouse walks out the door ... now, the entire world around you stops, you don't get to breath, answer the door, or finish watching your show while [s]he runs across town to fetch your beer. You just get to sit there not moving a muscle, and turning blue until you lose consciousness ... waking up some indefinite time later surrounded by EMTs and a spouse saying oh, hey, I got your beer.

That's exactly what happens when you insist on doing a synchronous server call.

sri