views:

71

answers:

2

Why in the following code I see the whole page at once ? Thanks !

HTML:

<div></div>

CSS:

div {
    width: 100px;
    height: 300px;
    border: 1px solid black;
    text-align: center;
}

Javascript:

$(function() {
    for (var i=0; i<15; i++) {
        sleep(100);
        $("div").append("<span>--- " + i + " ---<br /></span>");
    }

    function sleep(milliseconds) {
        var start = new Date().getTime();
        for (var i = 0; i < 1e7; i++) {
            if ((new Date().getTime() - start) > milliseconds){
                break;
            }
        }
    }
});
+3  A: 

Because Javascript on web browsers is single-threaded (although that's changing; see below) and virtually no browser updates its UI while the (main) Javascript thread is busy. All your sleep function does is block everything for 100ms, it doesn't let the browser do anything else like update its UI (e.g., doesn't "yield").

There are a couple of ways to do what you're trying to do:

  1. Use the new web workers stuff; but note that it's not widely-supported yet.

  2. Make your loop a function that calls itself via setTimeout. That way, your code is yielding to the browser and letting it update its UI.

Here's a simple example of how you might apply #2 above to your code:

$(function() {
    var i;

    doOne();

    function doOne() {
        $("div").append("<span>--- " + i + " ---<br /></span>");
        if (i++ < 15) {
            setTimeout(doOne, 0);   // <== Won't really be zero, browsers clamp it at ~10
        }
    }

});

If you have a lot of loop iterations (e.g., a couple of hundred instead of 15), it may well be worth doing a chunk of them on each iteration rather than yielding on each iteration; the yield takes a measureable time (typically ~10-15ms).

T.J. Crowder
Unless you use Web Workers if they are implemented in your browser: http://www.whatwg.org/specs/web-workers/current-work/
the_drow
@the_drow: Um...that was my way #1. :-)
T.J. Crowder
This recursion becomes problematic when the number of iterations is high (say 10000). Firefox 3.6.3 says "too much recursion". Do you have any workaround for that ?
Misha Moroshko
@Misha: There's no recursion at all in the example above. The call to `setTimeout` returns immediately, and then `doOne` returns immediately, unwinding the stack. Then after a brief interval, the browser calls `doOne` again, which does the next bit. Did you pehaps put `()` after `doOne` in the `setTimeout` call? They need *not* to be there, you're referencing the function, not calling it directly.
T.J. Crowder
@Misha: Looks like web workers are supported on current versions of Firefox, Chrome, Safari, and Opera, which only leaves IE (the biggest, I know) out in the cold. May well be worth looking into using them, at least optionally, with a fallback like the above for IE. I bet IE9 will have web workers.
T.J. Crowder
Now I understand that it is not a recursion :) I hope that I will be able to apply this technique in my program.Thanks a lot for your time !
Misha Moroshko
@Misha: Good deal, glad that was helpful. :-)
T.J. Crowder
It definitely was ! Good that there are people that are glad to help like you !
Misha Moroshko
A: 

You need to hand over some processing time to the UI since javascript is single threaded, like this:

$(function() {
    function appendDiv(i) {
        $("div").append("<span>--- " + i + " ---<br /></span>");
        if(i < 14) 
            setTimeout(function() { appendDiv(i+1); }, 100);  
    }
    appendDiv(0);
});​

You can see a demo here

You could also use an interval for what you want, like this:

$(function() {
    var i = 0;
    var interval = setInterval(function() {
        $("div").append("<span>--- " + i++ + " ---<br /></span>");
        if(i == 15) clearInterval(interval);
    }, 100);
});​
Nick Craver
Thank you for examples. setInterval is not what I need. I have a big calculation in Javascript that takes ~10sec, and I want to do some updates during this calculation to improve user's experience. As I mentioned above, the recursion with setTimeout becomes problematic when the number of iterations is high (say 10000). Do you have any other idea ?
Misha Moroshko
@Misha - What are you doing that takes that long? :) Can it not be sent with the page, pre-computed on the server? That would be much faster than JavaScript.
Nick Craver
I definitely could do the long task on server side, but this would require a big change in my program. I want to check if this can be done in Javascript.
Misha Moroshko
@Misha - I *strongly* suggest you do that, it's a much better user experience.
Nick Craver
OK, I'll take that into account. Thanks !
Misha Moroshko