93

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]?

Jonathan Hall
  • 75,165
  • 16
  • 143
  • 189
Gihan Lasita
  • 2,955
  • 13
  • 48
  • 65

10 Answers10

135

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.

T.J. Crowder
  • 1,031,962
  • 187
  • 1,923
  • 1,875
  • how about if i want to pass a variable in the function run – Master Yoda Feb 19 '16 at 13:11
  • @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. – T.J. Crowder Feb 19 '16 at 13:18
  • if i use the `setInterval(function() { run(argument); }, 200);` can i be able to use cleartimeout – Master Yoda Feb 19 '16 at 13:21
  • @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.) – T.J. Crowder Feb 19 '16 at 13:24
  • @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? – philosopher Oct 17 '20 at 04:15
  • 2
    @philosopher - 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]* – T.J. Crowder Oct 17 '20 at 07:39
  • *[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.) – T.J. Crowder Oct 17 '20 at 07:39
  • @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`). – philosopher Oct 17 '20 at 08:43
  • 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). – philosopher Oct 17 '20 at 08:48
  • @philosopher - No, it's in the right place, more in [this comment on your answer](https://stackoverflow.com/questions/8443151/how-to-stop-a-settimeout-loop/8443178?noredirect=1#comment113877756_64399209). – T.J. Crowder Oct 17 '20 at 08:49
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;
}
ThimoKl
  • 329
  • 2
  • 9
  • 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? – Mirror318 Dec 11 '16 at 06:06
  • That's right, @Mirror318. `setTimeout` returns a handler. That handler should be cleared with `clearTimeout`. – ThimoKl Dec 11 '16 at 10:50
  • 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. – Victor Stoddard Jan 27 '17 at 23:49
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
VALIKHAN
  • 391
  • 3
  • 3
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

Oyebola
  • 141
  • 4
2
var myVar = null;

if(myVar)
   clearTimeout(myVar);

myVar = setTimeout(function(){ alert("Hello"); }, 3000);
Mahdi Bashirpour
  • 17,147
  • 12
  • 117
  • 144
  • 2
    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. – RyanNerd Feb 19 '20 at 19:34
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
Neil Essy
  • 3,607
  • 1
  • 19
  • 23
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);
    }
});
Theo
  • 1,608
  • 1
  • 9
  • 16
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.

philosopher
  • 1,079
  • 2
  • 16
  • 29
  • *"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](https://stackoverflow.com/questions/8443151/how-to-stop-a-settimeout-loop/8443178?noredirect=1#comment113876967_8443178), you don't *have* to do that check before calling `clearTimeout` (and these days I don't), but it's not incorrect. – T.J. Crowder Oct 17 '20 at 08:49
  • @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);`. – philosopher Oct 17 '20 at 09:43
  • 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()`. – philosopher Oct 17 '20 at 09:43
  • 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. – philosopher Oct 17 '20 at 09:47
  • 1
    *"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 my`run`. – T.J. Crowder Oct 17 '20 at 09:51
  • 1
    @T.J.Crowder Ok true, thinking about it more, yes you are right, thanks for the discussion and the clarification! :) – philosopher Oct 17 '20 at 09:55
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 } ?>
Senthil
  • 426
  • 5
  • 11
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();
eeerahul
  • 1,629
  • 4
  • 27
  • 38
Pritom
  • 1,294
  • 8
  • 19
  • 37