1

Running into a bit of a funny problem. I have each day of the week hidden upon entry to my html (class="day"). By clicking on the Day, it expands to show everything under that day, by an AJAX call with $.post. This is all working well.

I figure the user may want to look at the whole week at once, so I have a button (id="expander") to show everything. However, when I call the function getSchedule() from within the #expander click, it loops through everything except the $.post in the getSchedule() function. Then it loops through the $.post.

$(document).ready(function()
{ 
    $('.day').click(function(){
        pass = $(this).prop('id');
        getSchedule(pass);
    });

    $('#expander').click(function(ex){
            $('.day').each(function(index){
                    pass = $(this).prop('id');
                    getSchedule(pass);
            });
            $('#expander').text('Hide All Days');
        ex.preventDefault();
        return false;       
    });
});

function getSchedule(elementid){
            dow = elementid.substr(-1);
            url = "standarddayofweek.php?d="+dow;
            $theday = $("#"+elementid);
            console.log("The day is "+$theday.prop('id'));
            $.post(url,function(data){
                    $theday.children('.dayschedule').html(data);
                }, "html"
            )
    }

The console response I get is:

The day is d1 
The day is d2
The day is d3 
The day is d4 
The day is d5 
The day is d6
The day is d7 
XHR finished loading: "http://127.0.0.1/standarddayofweek.php?d=1"
In the post function, the day is d7
XHR finished loading: "http://127.0.0.1/standarddayofweek.php?d=2"
In the post function, the day is d7
XHR finished loading: "http://127.0.0.1/standarddayofweek.php?d=3"
In the post function, the day is d7 
XHR finished loading: "http://127.0.0.1/standarddayofweek.php?d=4"
In the post function, the day is d7 
XHR finished loading: "http://127.0.0.1/standarddayofweek.php?d=5"
In the post function, the day is d7 
XHR finished loading: "http://127.0.0.1/standarddayofweek.php?d=6"
In the post function, the day is d7 
XHR finished loading: "http://127.0.0.1/standarddayofweek.php?d=7"
In the post function, the day is d7 

The watered down HTML:

<div id="expander">Click me to expand</div>

<div id="d1" class="day">
            <div class="dayschedule"></div>
</div>
<div id="d2" class="day">
            <div class="dayschedule"></div>
</div>

....

<div id="d7" class="day">
            <div class="dayschedule"></div>
</div>

What is going on? I have tried adding a delay, to see if the AJAX was happening too fast, but nothing changed.

Sablefoste
  • 4,032
  • 3
  • 37
  • 58
  • 1
    AJAX is asynchronous, so it runs basically when it feels like it. – Blender Oct 25 '12 at 22:26
  • Yes, but I have even added a `confirm` statement, to slow it down by my response timing. This is way too consistent in the response to be just random timing. Also, why is the function "split" between the non-AJAX calls and the AJAX calls? Also, by clicking on the individual days, it loads as expected. – Sablefoste Oct 25 '12 at 22:28
  • The whole loop is guaranteed to have run before the first post request is even processed. So your logs look correct: 7 logs from the loop and after that comes 7 logs from the $.post processing. – Esailija Oct 25 '12 at 22:32
  • Changing your post request to synchronous helps? async: false – r_31415 Oct 25 '12 at 22:36
  • @RobertSmith it also freezes the page, making it seem like the browser crashed especially when the server is not responding instantly and there's 7 requests too. If it was acceptable at all, people would never use async since it's an order of magnitude easier to code fully synchronous. – Esailija Oct 25 '12 at 22:38
  • Very thorough question. I like the way you ask ;) – Josiah Ruddell Oct 25 '12 at 22:42
  • @RobertSmith; you have the answer. By changing from $.post to $.ajax and switching to async:false, it works. I will post separately as an answer with my code. – Sablefoste Oct 26 '12 at 00:19
  • @SableFoste Glad it worked but why don't you modify your php so you can fetch all the information you need in a single request? You should avoid loops over requests because it won't scale in a site with lots of users. – r_31415 Oct 26 '12 at 01:28
  • @RobertSmith, I guess I could load and then slice and dice, but this design is for a back-end; so the frequency of this transaction is not too often, and won't be too big of a load to the system. In general, though, I agree, it is not best practices. – Sablefoste Oct 26 '12 at 13:06

4 Answers4

1

Javascript primarily runs with a single thread of execution see here. What this means to your code:

First off the .each is just a loop. Imagine the ajax requests are sent in a for loop.

for(...)
    $.post();

The ajax callback cannot run until the current execution stops. So your code will go through all of the elements, and log as it does, then the thread is released and the ajax callback can take control.

You should change your program so that you can get a list or batch of content back so that one request is only needed for several elements.

Community
  • 1
  • 1
Josiah Ruddell
  • 29,697
  • 8
  • 65
  • 67
1

Not all browsers will behave synchronously, so async: false is not a comprehensive solution. However, localizing the variables in getSchedule() is.

Try this:

function getSchedule(elementid) {
    var dow = elementid.substr(-1);
    var url = "standarddayofweek.php?d="+dow;
    var $theday = $("#"+elementid);
    console.log("The day is "+$theday.prop('id'));
    $.post(url, function(data) {
        $theday.children('.dayschedule').html(data);
    }, "html");
}

Thus, a closure is formed containing the (formal and informal) vars, which remain available to the inner function (the ajax success handler) when it fires asynchronously, even though the outer function has completed and returned.

Beetroot-Beetroot
  • 18,022
  • 3
  • 37
  • 44
  • This is a great suggestion, and works too! I wasn't aware of the non-cross browser compatibility. I hadn't considered variable scope, and I must admit I don't fully understand when Javascript decides to continue on with the `$.post` just because the variables are locally defined. Come to think of it, I remember learning somewhere in programming school that it is "always best to limit the scope of variables to the smallest set." I guess I should have paid more attention :D – Sablefoste Oct 26 '12 at 13:16
  • 1
    I can't think of a case where sending ajax requests in a loop is a good idea. You should re-think your approach and reduce the number of requests needed to gather all of the data. – Josiah Ruddell Oct 26 '12 at 19:08
  • +1. That's a good point. Both server-side and client-side code would need to be modified. Of course, if you are relying on a 3rd-party web-service, then you may have no choice, depending on how flexible the service's API is (and how well it's documented). – Beetroot-Beetroot Oct 26 '12 at 19:40
0

JavaScript is not multi-threaded. You're function must return before asynchronous stuff can happen.

By repeatedly calling $.post without returning the flow of execution to the browser, you are effectively queueing up asynchronous actions which will run when your code has finished.

If, for example, your code were to never return by looping forever, none of your $.posts would ever be invoked.

user229044
  • 232,980
  • 40
  • 330
  • 338
0

Thanks to @RobertSmith, the answer was switching from $.post to $.ajax. I just changed:

$.post(url,function(data){
                    $theday.children('.dayschedule').html(data);
                }, "html"
            )

to

$.ajax({
    url: url,
    async:false,
    success: function(data) {
     $theday.next().children('.dayschedule').html(data);
    }
});

Note the async:false; that is what required the switch.

Thanks to everyone for their comments!

Sablefoste
  • 4,032
  • 3
  • 37
  • 58