How to stop a setTimeout loop?
Asked Answered
G

10

98

I'm trying to build a loading indicator with a image sprite and I came up with this function

function setBgPosition() {
   var c = 0;
    var numbers = [0, -120, -240, -360, -480, -600, -720];
    function run() {
       Ext.get('common-spinner').setStyle('background-position', numbers[c++] + 'px 0px');
        if (c<numbers.length)
        {
            setTimeout(run, 200);
        }else
        {
            setBgPosition();
        }
    }
    setTimeout(run, 200);
}

so the out put is looks like this

http://jsfiddle.net/TTkre/

I had to use setBgPosition(); inside else to keep this running in a loop so now my problem is how to stop this loop once I want [load finished]?

Gaylenegayler answered 9/12, 2011 at 8:58 Comment(2)
do you want to stop loop when if (c<numbers.length) is true?Gaskin
no if i dont use else, animation work only once thoughGaylenegayler
C
138

setTimeout returns a timer handle, which you can use to stop the timeout with clearTimeout.

So for instance:

function setBgPosition() {
    var c = 0,
        timer = 0;
    var numbers = [0, -120, -240, -360, -480, -600, -720];
    function run() {
        Ext.get('common-spinner').setStyle('background-position', numbers[c++] + 'px 0px');
        if (c >= numbers.length) {
            c = 0;
        }
        timer = setTimeout(run, 200);
    }
    timer = setTimeout(run, 200);

    return stop;

    function stop() {
        if (timer) {
            clearTimeout(timer);
            timer = 0;
        }
}

So you'd use that as:

var stop = setBgPosition();
// ...later, when you're ready to stop...
stop();

Note that rather than having setBgPosition call itself again, I've just had it set c back to 0. Otherwise, this wouldn't work. Also note that I've used 0 as a handle value for when the timeout isn't pending; 0 isn't a valid return value from setTimeout so it makes a handy flag.

This is also one of the (few) places I think you'd be better off with setInterval rather than setTimeout. setInterval repeats. So:

function setBgPosition() {
    var c = 0;
    var numbers = [0, -120, -240, -360, -480, -600, -720];
    function run() {
        Ext.get('common-spinner').setStyle('background-position', numbers[c++] + 'px 0px');
        if (c >= numbers.length) {
            c = 0;
        }
    }
    return setInterval(run, 200);
}

Used like this:

var timer = setBgPosition();
// ...later, when you're ready to stop...
clearInterval(timer);

All of the above notwithstanding, I'd want to find a way to make setBgPosition stop things itself, by detecting that some completion condition has been satisfied.

Cordite answered 9/12, 2011 at 9:0 Comment(10)
how about if i want to pass a variable in the function runSuboceanic
@BhawinParkeria: You either wrap it in a function like this; setInterval(function() { run(argument); }, 200); or you use Function#bind like this: setInterval(run.bind(null, argument), 200);. The former is useful when you need to see the then-current value of the argument each time it runs, the latter when you want to "bake in" the current value of argument and keep using that even if argument changes later.Cordite
if i use the setInterval(function() { run(argument); }, 200); can i be able to use cleartimeoutSuboceanic
@BhawinParkeria: Yes, why wouldn't you? The handle isn't related to which function you passed into setInterval. (I'd use clearInterval rather than clearTimeout, but they will do the same thing.)Cordite
@T.J.Crowder Elegant solution but can you explain why you need the if (timer) statement within the stop function function? Why can't you just call clearTimeout directly? Further, does this still work as expected if the run function has await statements within it before timer = setTimeout(run, 200); is called?Bailey
@Bailey - You don't need the if. :-) I always used to put it there, but 0 is (now) clearly defined as an invalid timer handle and clearTimeout is (now) clearly defined as not throwing any errors or similar on invalid timer handles, so you could get rid of that if and keep the body without changing anything. (And these days, I do.) Re await in run: Just beware of two things: 1. If you have await in run, then by definition run must be an async function, which means it returns a promise, which means that you're returning a promise to something (the code in the [cont'd]Cordite
[continuing] timer stuff) that doesn't do anything with that promise. That means you have to make sure the entire body of the function is wrapped in try/catch so that the promise it returns is never rejected, since if it is you'll get an unhandled rejection error. 2. The timer will start as of when setTimeout is called, so if run waits for other asynchronous things to happen, it may run less often than every 200ms. (But that could happen anyway.)Cordite
@T.J.Crowder Correct me if I am wrong but I think the if (timer) statement has been mistakenly placed within the stop() function call. It should instead be placed within the run() function call like if (timer) timer = setTimeout(run, 200). This prevents future setTimeout statements from being run right after stop() is called (which sets timer to 0).Bailey
I posted an updated answer below with how I modified your code idea to make it work for me as I intended with async calls. In my case I wanted the timer to continue running my function calls until an external condition in my code is met, which then sets runFutureSetTimeouts to false (see code below if interested).Bailey
@Bailey - No, it's in the right place, more in this comment on your answer.Cordite
S
13

I know this is an old question, I'd like to post my approach anyway. This way you don't have to handle the 0 trick that T. J. Crowder expained.

var keepGoing = true;

function myLoop() {
    // ... Do something ...

    if(keepGoing) {
        setTimeout(myLoop, 1000);
    }
}

function startLoop() {
    keepGoing = true;
    myLoop();
}

function stopLoop() {
    keepGoing = false;
}
Sanborn answered 22/9, 2013 at 3:2 Comment(3)
I know this is an old answer, but what happens if you stopLoop and startLoop within the 1000 timeout? Won't you now have two loops running, both checking that keepGoing is true?Hackworth
That's right, @Mirror318. setTimeout returns a handler. That handler should be cleared with clearTimeout.Sanborn
This works but canceling the timer itself is more accurate because you don't have to wait until the loop to finish which, in your example, could be a a full second long.Skat
F
6

SIMPLIEST WAY TO HANDLE TIMEOUT LOOP

function myFunc (terminator = false) {
    if(terminator) {
        clearTimeout(timeOutVar);
    } else {
        // do something
        timeOutVar = setTimeout(function(){myFunc();}, 1000);
    }
}   
myFunc(true); //  -> start loop
myFunc(false); //  -> end loop
Fantoccini answered 26/4, 2017 at 7:1 Comment(1)
correct me if I am wrong, but isn't the logic reversed?Sunless
I
4

Try something like this in case you want to stop the loop from inside the function:

let timer = setInterval(function(){
  // Have some code to do something

  if(/*someStopCondition*/){ 
    clearInterval(timer)
  }
},1000);

You can also wrap this inside a another function, just make sure you have a timer variable and use clearInterval(theTimerVariable) to stop the loop

Impend answered 2/6, 2020 at 20:13 Comment(0)
M
3
var myVar = null;

if(myVar)
   clearTimeout(myVar);

myVar = setTimeout(function(){ alert("Hello"); }, 3000);
Malanie answered 19/2, 2020 at 10:14 Comment(1)
This may answer the question. However, code only answers are not as useful as answers that document the code or have an detailed explanation on why this code is the solution to the question.Monkeypot
P
2

You need to use a variable to track "doneness" and then test it on every iteration of the loop. If done == true then return.

var done = false;

function setBgPosition() {
    if ( done ) return;
    var c = 0;
    var numbers = [0, -120, -240, -360, -480, -600, -720];
    function run() {
        if ( done ) return;
        Ext.get('common-spinner').setStyle('background-position', numbers[c++] + 'px 0px');
        if (c<numbers.length)
        {
            setTimeout(run, 200);
        }else
        {
            setBgPosition();
        }
    }
    setTimeout(run, 200);
}

setBgPosition(); // start the loop

setTimeout( function(){ done = true; }, 5000 ); // external event to stop loop
Playground answered 9/12, 2011 at 9:3 Comment(0)
S
1

As this is tagged with the extjs tag it may be worth looking at the extjs method: http://docs.sencha.com/extjs/6.2.0/classic/Ext.Function.html#method-interval

This works much like setInterval, but also takes care of the scope, and allows arguments to be passed too:

function setBgPosition() {
    var c = 0;
    var numbers = [0, -120, -240, -360, -480, -600, -720];
    function run() {
       Ext.get('common-spinner').setStyle('background-position', numbers[c++] + 'px 0px');
        if (c<numbers.length){
            c=0;
        }
    }
    return Ext.Function.interval(run,200);
}

var bgPositionTimer = setBgPosition();

when you want to stop you can use clearInterval to stop it

clearInterval(bgPositionTimer);

An example use case would be:

Ext.Ajax.request({
    url: 'example.json',

    success: function(response, opts) {
        clearInterval(bgPositionTimer);
    },

    failure: function(response, opts) {
        console.log('server-side failure with status code ' + response.status);
        clearInterval(bgPositionTimer);
    }
});
Silvanus answered 23/2, 2017 at 20:50 Comment(0)
B
0

I am not sure, but might be what you want:

var c = 0;
function setBgPosition()
{
    var numbers = [0, -120, -240, -360, -480, -600, -720];
    function run()
    {
        Ext.get('common-spinner').setStyle('background-position', numbers[c++] + 'px 0px');
        if (c<=numbers.length)
        {
            setTimeout(run, 200);
        }
        else
        {
            Ext.get('common-spinner').setStyle('background-position', numbers[0] + 'px 0px');
        }
    }
    setTimeout(run, 200);
}
setBgPosition();
Bissonnette answered 9/12, 2011 at 8:58 Comment(0)
B
0

In the top answer, I think the if (timer) statement has been mistakenly placed within the stop() function call. It should instead be placed within the run() function call like if (timer) timer = setTimeout(run, 200). This prevents future setTimeout statements from being run right after stop() is called.

EDIT 2: The top answer is CORRECT for synchronous function calls. If you want to make async function calls, then use mine instead.

Given below is an example with what I think is the correct way (feel to correct me if I am wrong since I haven't yet tested this):

const runSetTimeoutsAtIntervals = () => {
    const timeout = 1000 // setTimeout interval
    let runFutureSetTimeouts // Flag that is set based on which cycle continues or ends

    const runTimeout = async() => {
        await asyncCall() // Now even if stopRunSetTimeoutsAtIntervals() is called while this is running, the cycle will stop
        if (runFutureSetTimeouts) runFutureSetTimeouts = setTimeout(runTimeout, timeout)
    }

    const stopRunSetTimeoutsAtIntervals = () => {
        clearTimeout(runFutureSetTimeouts)
        runFutureSetTimeouts = false
    }

    runFutureSetTimeouts = setTimeout(runTimeout, timeout) // Set flag to true and start the cycle
    return stopRunSetTimeoutsAtIntervals
}

// You would use the above function like follows.
const stopRunSetTimeoutsAtIntervals = runSetTimeoutsAtIntervals() // Start cycle
stopRunSetTimeoutsAtIntervals() // Stop cycle

EDIT 1: This has been tested and works as expected.

Bailey answered 17/10, 2020 at 5:8 Comment(6)
"In the top answer, I think the if (timer) statement has been mistakenly placed within the stop() function call" No, it's in the right place. It's there so that when you call stop, it stops the timer if the timer is running. As I explained here when you asked about it, you don't have to do that check before calling clearTimeout (and these days I don't), but it's not incorrect.Cordite
@T.J.Crowder Thanks for the response. You are correct that the if (timer) statement doesn't change anything if placed or removed from within the stop() function. However, I stand by my statement that if statement still needs to be placed within the run() function like if (timer) timer = setTimeout(run, 200);.Bailey
To explain this, consider the situation in your code where stop() is called during when any code between and including lines Ext.get()... to c = 0; is being executed. In this case, clearTimeout will be clearing the previously set timer and not the one that is going to be executed right after line c = 0;, which would result in your loop continuing even after you have called stop().Bailey
This is of course more likely to occur if the lines between Ext.get()... to c=0; were instead an await async function that might not complete its execution immediately. The code I have given in my answer above, covers for that edge case and does not let it happen.Bailey
"To explain this, consider the situation in your code where stop() is called during when any code between and including lines Ext.get()... to c = 0; is being executed. " That cannot happen in my code, because run is not an async function. It can happen in yours because it is an async function. So it makes sense in your runTimeout, but it doesn't make sense in myrun.Cordite
@T.J.Crowder Ok true, thinking about it more, yes you are right, thanks for the discussion and the clarification! :)Bailey
U
0

When the task is completed and you can display the task (image in your case), on the next refresh don't send the javascript. If your server is using PHP.

<?php if (!$taskCompleted) { ?>
<script language="javascript">
setTimeout(function(){
   window.location.reload(1);
}, 5000);
</script>
<?php } ?>
Unionize answered 1/1, 2022 at 4:42 Comment(0)

© 2022 - 2024 — McMap. All rights reserved.