简体   繁体   中英

setTimeout is causing infinite while loop

This is my first time using a while loop in a practical application so please forgive my ignorance.

I am creating a webpage that demonstrates the costs of using a lightbulb over time.

At this stage I am trying to use a while loop to update and display the number of hours that have passed since the user clicked the light switch. (1 hour represents 1 second of realtime)

When I set breakpoints on firebug, everything operates normally until I get to the setTimeout method within my while loop. After it breaks at the setTimeout method and I click continue, it immediately breaks at the same place again, without actually executing anything else.

When I don't set breakpoints, it freezes firefox and I have to stop script execution.

I rechecked to make sure that I am using setTimeout properly. Now I'm not even sure where to check or what to search for because I don't understand whats going wrong. Even just a hint of what I might check or research would be immensely helpful.

I have tried to comment the code as verbosely as possible. I'll be happy to clarify something if needed.

I would highly recommend taking a look at the jsfiddle:

JS FIDDLE

But here is my code:

My JS

$(document).ready(function () {
    //set image to default off position
    $('#lightswitch').css("background-image", "url(http://www.austinlowery.com/graphics/offswitch.png)");

    // setup the lifetime hours of the lightbulb for later use
    var lifetimeHours = 0;
    // setup function to update the calculated lifetime hours number on the webpage to
    // be called later
    function updateLifetimeHoursHtml (lifetimeHours) {
        $('#lifetimeHours').html(lifetimeHours);
    }
    // set up function to to send to setTimeout
    function updateNumbers () {
        // increment lifetimeHours by one
        lifetimeHours = lifetimeHours++;
        // call function to update the webpage with the new number result
        updateLifetimeHoursHtml(lifetimeHours);
    }

    // When the lightswitch on the webpage is clicked, the user should see the
    // lifetime hours update every second until the user clicks the switch again
    // which will then display the off graphic and pause the updating of the lifetime
    // hours
    $('#lightswitch').click(function(){
        // if the lightswitch is off:
        if ($('#lightswitch').attr('state') == 'off') {
            // set switch to on
            $('#lightswitch').attr('state', 'on');
            // update graphic to reflect state change
            $('#lightswitch').css("background-image", "url(http://austinlowery.com/graphics/onswitch.png)");
            // start updating the lifetime hours number on the webpage
            // while the #lightswitch div is in the on state:
            while ($('#lightswitch').attr('state') == 'on'){
                //call update numbers every second
                setTimeout('updateNumbers()', 1000);
            }
        // the lightswich was not in the off state so it must be on
        }else{
            // change the state of the switch to off
            $('#lightswitch').attr('state', 'off');
            // update graphic to reflect state change
            $('#lightswitch').css("background-image", "url(http://austinlowery.com/graphics/offswitch.png)");
        };
    });
});

My HTML

<div id="container">
    <div id="lightswitch" state="off">&nbsp;</div>
        <span>After </span><span id="lifetimehours">0</span><span> lifetime hours:</span>
        <br><br>
        <span><b>You have spent:</b></span>
        <br><br>
        <span id="dollaramoutelectricity"></span><span> on electricty</span>
        <br>
        <span id="mainttime"></span><span> on maintenace</span>
        <br>
        <span id="dollaramountbulbs"></span><span> on replacement bulbs</span>
        <br><br>
        <span><b>You have:</b></span>
        <br><br>
        <span>Produced </span><span id="amountgreenhousegasses"></span><span> of greenhouse gasses</span>
        <br>
        <span>Sent </span><span id="amounttrash"></span><span> of trash to the dump</span>
        <br>
        <span>Used </span><span id="amountelectricty"></span><span> of electricity</span>

</div>
var switchTimer;

$('#lightswitch').click(function(){
    if ($('#lightswitch').attr('state') == 'off') {
        $('#lightswitch').attr('state', 'on');
        switchTimer = setInterval(updateNumbers, 1000);
    } else {
        $('#lightswitch').attr('state', 'off');
        $('#lightswitch').css("background-image", "url(http://austinlowery.com/graphics/offswitch.png)");
        clearInterval(switchTimer);
    };

Javascript is an event based language. This means that codes doesn't run constantly. It only runs when there is an event. By using a while loop you have basically frozen it - the javascript is constantly running inside that loop. That's fine for languages like C, which have to have something running all the time.

But it's a mistake for javascript. For javascript you have to code it to respond to an event, then stop. The setInterval constantly generates an event for you, running a function every xx milliseconds.

In between runs of the timer no code is running! This is important to remember.

JavaScript uses an event loop. Because your while loop never yields ( #lightswitch 's state will forever be on because the infinite loop locks the UI, preventing the user from turing it off), the event loop never runs again, and the callback you register with setTimeout never gets a chance to execute.

What you're really looking to do is update your counter with a setInterval function.

setInterval(updateNumbers, 1000);

Based on yngum's answer,

I think it's better like this:

if ($('#lightswitch').attr('state') == 'off') {
    $('#lightswitch').attr('state', 'on');
    $('#lightswitch').css("background-image", "url(http://austinlowery.com/graphics/onswitch.png)");
    updateNumbers();
}

And then

function updateNumbers () {
    lifetimeHours++;
    updateLifetimeHoursHtml(lifetimeHours);
    if($('#lightswitch').attr('state') == 'on'){
        setTimeout(updateNumbers,1000);
    }
}

See it here: http://jsfiddle.net/tdS3A/24/

But if you want maximum precision, you should store new Date().getTime() , because doing setTimeout or setInterval of 1 second doesn't ensure you that it will be called each second...

function updateNumbers () {
    // call function to update the webpage with the new number result
    updateLifetimeHoursHtml(lifetimeHours+(new Date().getTime()-timerStart)/1000);
    // call updateNumbers() again
    if($('#lightswitch').attr('state') == 'on'){
        timer=setTimeout(updateNumbers,1000);
    }
}
$('#lightswitch').click(function(){
    // if the lightswitch is off:
    if ($('#lightswitch').attr('state') == 'off') {
        // set switch to on
        $('#lightswitch').attr('state', 'on');
        // update graphic to reflect state change
        $('#lightswitch').css("background-image", "url(http://austinlowery.com/graphics/onswitch.png)");
        // update the lifetime hours number on the webpage
        timerStart=new Date().getTime();
        timer=setTimeout(updateNumbers,1000);
        // the lightswich was not in the off state so it must be on
    }else{
        // change the state of the switch to off
        lifetimeHours+=(new Date().getTime()-timerStart)/1000;
        clearTimeout(timer);
        $('#lightswitch').attr('state', 'off');
        // update graphic to reflect state change
        $('#lightswitch').css("background-image", "url(http://austinlowery.com/graphics/offswitch.png)");
    };
});

See it here: http://jsfiddle.net/tdS3A/26/

Even if you want the number with maximum precision because you want the calculations to be precise, maybe you want to round that value when you show it to the user. Then, use

function updateLifetimeHoursHtml (lifetimeHours) {
    $('#lifetimehours').html(Math.round(lifetimeHours));
}

Since setTimeout is generally preferred over setInterval, another solution is

Change

while ($('#lightswitch').attr('state') == 'on'){
    //call update numbers every second
    setTimeout('updateNumbers()', 1000);

To

setTimeout(function() {  
    updateNumbers();
    if ($('#lightswitch').attr('state') == 'on') 
        setTimeout(arguments.callee, 1000);
    }, 0);

This will check ur lightswitch every second, and stop when it is off.

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM