4

I have the following code:

<!DOCTYPE html PUBLIC "-//W3C//DTD HTML 4.01//EN"
    "http://www.w3.org/TR/html4/strict.dtd">
<html>
<head>
    <meta http-equiv="Content-Type" content="text/html; charset=UTF-8">
    <title></title>
    </head>
<body>
<div id="logger"></div>
<script>

function log(txt) {
    document.getElementById('logger').innerHTML += txt + '<br>';
}

var int = 10;
var a= setTimeout(function(){
    a = null;
    log("A fired!");
    clearTimeout(b);
    b = null;
}, int);

var b = setTimeout(function(){
    b = null;
    log("B fired!");
    clearTimeout(a);
    a = null;
}, int);


</script>

</body>
</html>

Both timeout callbacks should prevent another another one from firing. In Opera, FF and Chrome only first one (that prints ″A fired″) is executed. But when I run the same code in IE6 and IE8, both callbacks are executed. Is that some error in my scriupt or is that one of those bugs that these browsers are full of? Do clearTimeout()/clearInterval() guarantee that callback won't be called after their invocation?

Egorinsk
  • 53
  • 1
  • 3
  • As @qwerty points out your assignment of `var a` and `var b` are so close together I would think that the slightly slower JavaScript engine in IE is taking longer to complete your `log` statement and so the `clearTimeout` call is occurring after the function has already been called. Try `2 * int` for the `setTimeout` call when assigning `var b` and you won't see it called as there is enough of a delay to complete the `var a` invocation and clear the timeout. – Dave Anderson May 02 '11 at 05:23
  • _Try 2 * int for the setTimeout call_ — that's no fun :) In a complex script you cannot guarantee that timeouts won't fire at the same time. – Egorinsk May 02 '11 at 06:34
  • @Egorinsk: Perhaps you could change the accepted answer for this question? – robocat Apr 23 '13 at 02:48
  • @robocat as your answer scored more points I have changed the accepted answer. – Egorinsk May 04 '13 at 20:39

2 Answers2

3

I think what is happening is that:

  • JavaScript has an event queue.
  • IE processes the timeouts, and queues two events.
  • The first timeout event is processed, and clearTimeout is called for B. However the event for B is already queued, so it still gets fired.
  • the second timeout event is processed, and clearTimeout is called for A.

I suspect that in IE, the event gets queued and calling clearTimeout does not remove the event from the event queue.

It is also possible there is just funkyness in how IE pushes simultaneous timeouts onto the queue... Diagnosing the underlying cause could be acheived by using two different timeouts, using 100% CPU processing loops for x time, and by queuing/slotting in other events (maybe can inject events into queue using window.postMessage() and catch them with window.onMessage()).

I have modified your existing code to demonstrate the problem better. It queues the log items rather than doing them immediately, because calling display() can cause layout or rendering to occur, which can easily introduce other funky interference.

Edit: You can test this using http://jsbin.com/ucukez/2 - if the browser has the fault then you get "in A timeout fired" and "in B timeout fired".

Edit: This was fixed in IE9 - I couldn't reproduce in IE9/IE10/IE11.

The HTML is:

<!DOCTYPE html>
<html>
<head>
    <meta http-equiv="Content-Type" content="text/html; charset=UTF-8">
    <title>setTimeout queued test</title>
    <script>
        function display(txt) {
            document.getElementById('logger').innerHTML += txt + '<br>';
        }

        var log = {
            items: [],
            push: function(text) {
                this.items.push(text);
            },
            display: function() {
                var items = this.items;
                this.items = [];
                for (var i = 0; i < items.length; i++) {
                    display(items[i]);
                }
            }
        };

        function startTest() {
            var ms = 10;

            display("startTest()");
            log.push('before A setTimeout');
            var a = setTimeout(function(){
                log.push('in A timeout fired');
                display("A fired!");
                log.push('in A clear timer B');
                clearTimeout(b);
                log.push('in A cleared timer B');
            }, ms);
            log.push('after A setTimeout');

            log.push('before B setTimeout');
            var b = setTimeout(function(){
                log.push('in B timeout fired');
                display("B fired!");
                log.push('in B clear timer A');
                clearTimeout(a);
                log.push('in B cleared timer A');
            }, ms);
            log.push('after B setTimeout');

            setTimeout(function(){
                display("");
                display("Displaying logged items:");
                log.display();
            },1000);
        }
    </script>
</head>
<body onload="startTest()">
    <div id="logger"></div>
</body>
</html>
robocat
  • 5,293
  • 48
  • 65
  • Thank you for your answer. That seems to be one of numerous IE oddities, so I guess the only work around here is just adding some flags and checking them before running some action. – Egorinsk Aug 25 '11 at 10:33
0

You're setting the timeouts to occur at exactly the same time, and since they are both forked processes you get inconsistent results

Your best bet is to first test to see if the timeout is still valid like this:

var int = 10;
var a= setTimeout(function(){
    if (!a) return;
    a = null;
    log("A fired!");
    clearTimeout(b);
    b = null;
}, int);

var b = setTimeout(function(){
    if (!b) return;
    b = null;
    log("B fired!");
    clearTimeout(a);
    a = null;
}, int);
qwertymk
  • 34,200
  • 28
  • 121
  • 184
  • 2
    But javascript is single threaded, those 2 functions cannot run at the same time! That's weird. But your code seems to fix the bug. – Egorinsk May 02 '11 at 05:56
  • 1
    @Egorinsk: I agree but that's the way of M$, btw if you feel that I answered your question, you can click on the green check next to my answer – qwertymk May 02 '11 at 06:51