20

I've always thought that, since JavaScript was single-threaded, I could attach event handlers without worrying about the handlers getting executed while I was in the middle of executing code. To my surprise, I found that they could. According to this answer, the 'Unresponsive Script' dialog box can cause events to be raised while the script is still running.

I tested this with the following code:

<script>
    function loop() {
        var cond;
        onblur = function (event) {
            cond = false;
        };
        cond = true;
        while (cond)
            ;
        alert('loop exited!');
    }
</script>
<button onclick="loop()">loop()</button>

(jsFiddle)

In Firefox 11.0, the function prints "loop exited" after clicking continue. The loop seems to be paused, with events allowed to execute. This is akin to a Unix signal, which temporarily changes the context of the target thread. But it is much more dangerous, as it allows external state to be altered.

Is this a bug? Should I no longer depend on the single-flow-of-execution model of JavaScript and ensure that all my scripts are re-entrant? Or is it a flaw not worth pursuing despite a major browser allowing this to happen?

Brian Tompsett - 汤莱恩
  • 5,753
  • 72
  • 57
  • 129
Timothy003
  • 2,348
  • 5
  • 28
  • 33
  • 2
    I guess the real question would be: "what does the standard call for." – Jeffrey Sweeney Mar 19 '12 at 22:11
  • 1
    I think it's better to think of the "pausing" that happens with some built-in behaviors (`alert()` etc) as taking the form of the interpreter forcing a "yield" from the function running at the time, and then, when the "pause" is finished, resuming execution. In other words, thinking of the function as having actually returned while the "pause" is in progress, and then being continued as a continuation is continued. That way, the event handling makes a lot more sense, as it's just like ordinary event handling. – Pointy Mar 19 '12 at 22:18
  • 7
    Getting an "unresponsive script" prompt is hardly a normal thing you should design for. Your app is broken is the user ever gets one of those dialogs. You should design your scripting so the "unresponsive script" prompt never occurs and then you won't have to worry about re-entrancy. It does appear to me to be a somewhat minor bug that a major browser allows events to fire when in that prompt, but I think it's much more important to fix your code so the prompt never occurs and this is never an issue. – jfriend00 Mar 19 '12 at 22:18
  • 1
    @jfriend00 The problem isn't that my script is long-running; it's the idea that a browser can arbitrarily interrupt running code and execute a user callback. Of course, that idea would have no basis if the Unresponsive Script behavior turned out to be a bug, but we don't know that. If you do, please submit that as an answer. :) – Timothy003 Mar 20 '12 at 03:26
  • The answer you linked to is pretty clear, isn't it? If it helps, think of it like this: in order to display a dialog, the browser's event loop needs to be running. In theory, the window's message queue could be plugged for scripting while pumping for everything else, but that's harder than it sounds when the entire browser UI is XUL and JS. – ephemient Mar 20 '12 at 03:30
  • @Timothy003 - Other than a script that is running too long, when does a browser arbitrarily interrupt running code and execute a user callback? I didn't see any examples of that in that article. I did see examples where you execute some command (like setting focus on a field or putting up an alert prompt) that itself triggers events or can allow other events to be called. That is normal. That isn't arbitrary interruption. That's entirely predictable and expected (by me anyway) and it's easy to understand and know that it can happen. It's also relatively easy to design for. – jfriend00 Mar 20 '12 at 05:15
  • @jfriend00 What's to stop the browser from popping up the Unresponsive Script dialog between any two statements in my code? As far as I know, the Unresponsive Script warning is [timing-sensitive,](http://kb.mozillazine.org/Unresponsive_Script_Warning) and the standard doesn't define how long each expression takes to execute. I can't guarantee that my script will never, ever trigger the warning. – Timothy003 Mar 20 '12 at 16:30
  • @Timothy003 - as my comment said "other than a script that is running too long". Design your scripts so they don't trip any "unresponsive script" dialog and you should be fine. If you want to spend double the development time making your scripts bulletproof from extra event processing because of a random "unresponsive scripts" prompt, go right ahead. IMO, it's much better to spend time making sure your script NEVER gets an "unresponsive script" prompt in any reasonable browser. – jfriend00 Mar 20 '12 at 16:37
  • @Timothy003 - Browsers don't mean you malicious intent with random "unresponsive script" dialogs. This is not something to fear. They all have a timeout value intended to protect the user from stuck or errant scripts. If your scripts are in danger of triggering this, then run a bunch of tests and find out what the trigger values are in the popular browsers and make sure your code easily stays below that limit. – jfriend00 Mar 20 '12 at 16:38
  • @jfriend00 Saying that my app is broken if the user ever gets one of those dialogs is like saying my car design is broken if the driver ever crashes it. I agree that we should write our scripts such that they don't make lots of computations, but doing that doesn't eliminate the possibility of a browser running our code for more than a few milliseconds. And before you say it isn't likely, look at all the computers loaded with bloatware, running out of memory, and taking literally _minutes_ to open the start menu. Robust applications can't assume anything beyond what the standard defines. – Timothy003 Mar 24 '12 at 18:24
  • @Timothy003 - If you want to design every single code path in your app to handle an unplanned re-entrancy because of a code running too long dialog, feel free to do that. It's my judgement that that is rarely the most effective place to put your development effort. You are free to have a different opinion. – jfriend00 Mar 24 '12 at 22:23

2 Answers2

2

So yeah, if you create an infinite loop you will hang your JavaScript. Diff browsers will handle this differently. Firefox 11 for me throws up a window saying your script has hung. Chrome is just spinning for me at the moment.

This should prove the point. Never calls alert() in FF11 or Chrome 17.

while(true){}
alert("sucks");

http://jsfiddle.net/JCKRt/1/

You asked about sync statements in JavaScript. There are a few other synchronous statements that will block the execution of JavaScript like alert(), confirm(), synchronous ajax calls and more.

You usually you want to avoid any sort of synchronous stuff in JavaScript! If you want things to pause, you may want to re-think the design. JavaScript is event driven. You don't need it to spin in a while loop, because nothing on the page will work including any events like clicks, etc.

Jamund Ferguson
  • 16,721
  • 3
  • 42
  • 50
  • Related: https://bugzilla.mozilla.org/show_bug.cgi?id=340345 Now there's at least one special case in Mozilla to plug timers while a sync XHR is in progress, but the general case is less clear. – ephemient Mar 20 '12 at 03:33
  • If you need to spin a while loop - without blocking UI events - you could use a web worker: https://developer.mozilla.org/En/Using_web_workers#Performing_computations_in_the_background – Will Peavy Mar 20 '12 at 04:08
  • @Timothy003 Thought I'd replace the FUD with real information :) – Jamund Ferguson Mar 20 '12 at 04:53
0

I think that the problem comes in when you start dealing with events.

Rather than using a while loop, consider using a recursive function.

Here are some modifications I made to your code that should execute as desired.

<head>
    <script>
    function loop(that) {
        var cond;
        that.onblur = function (event) {
            cond = false;
        };
        cond = true;
        var loop = 0
        var xy = function x (){
            if(cond == true){
                loop++;         
                setTimeout(function(){xy()},0);
            } else {
                alert('loop exited! - '+loop);
                return true;
            }
        }
        xy();
    }
    </script>
</head>
<body>
    <button onclick="this.focus(); loop(this)">loop()</button>
</body>

Using setTimeout() with a 0 delay should allow any events to jump in without any issues. Your loop code won't be executed nearly as quickly but you will just have to test it and see.

fie
  • 407
  • 3
  • 10