如何停止 setTimeout 循环?

我试图建立一个加载指示器与图像精灵,我想出了这个功能

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);
}

输出是这样的

Http://jsfiddle.net/ttkre/

我必须使用 setBgposition () ; 在 else 中保持这个循环运行,所以现在我的问题是,一旦我想[装载完成] ,如何停止这个循环?

174514 次浏览

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.

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

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();

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;
}

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);
}
});

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
var myVar = null;


if(myVar)
clearTimeout(myVar);


myVar = setTimeout(function(){ alert("Hello"); }, 3000);

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

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.

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