【问题标题】:How to stop a setTimeout loop?如何停止 setTimeout 循环?
【发布时间】:2012-01-16 14:34:38
【问题描述】:

我正在尝试使用图像精灵构建一个加载指示器,我想出了这个功能

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 里面保持这个循环运行所以现在我的问题是一旦我想要[加载完成]如何停止这个循环?

【问题讨论】:

  • 当 if (c
  • 不,如果我不使用 else,动画只能工作一次

标签: javascript css extjs settimeout


【解决方案1】:

setTimeout 返回一个计时器句柄,您可以使用它来通过clearTimeout 停止超时。

例如:

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

所以你可以这样使用:

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

请注意,我没有让setBgPosition 再次调用自身,而是将其设置为c 回到0。否则,这是行不通的。另请注意,我使用0 作为超时未挂起时的句柄值; 0 不是来自 setTimeout 的有效返回值,因此它是一个方便的标志。

这也是我认为您最好使用setInterval 而不是setTimeout 的(少数)地方之一。 setInterval 重复。所以:

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

这样使用:

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

尽管如此,我还是想找到一种方法让setBgPosition 停止事情本身,通过检测某些完成条件已经满足。

【讨论】:

  • 如果我想在函数运行中传递一个变量呢
  • @BhawinParkeria:您可以将其包装在这样的函数中; setInterval(function() { run(argument); }, 200); 或者你像这样使用Function#bindsetInterval(run.bind(null, argument), 200);。当您需要在每次运行时查看参数的当前值时,前者很有用,后者在您想要“烘焙”argument 的当前值并继续使用它时,即使 argument 稍后更改.
  • 如果我使用setInterval(function() { run(argument); }, 200); 可以使用cleartimeout
  • @BhawinParkeria:是的,你为什么不呢?句柄与您传递给setInterval 的函数无关。 (我会使用clearInterval 而不是clearTimeout,但他们会做同样的事情。)
  • @philosopher - 你不需要if。 :-) 我总是把它放在那里,但是0(现在)被明确定义为无效的计时器句柄,clearTimeout(现在)被明确定义为在无效的计时器句柄上不抛出任何错误或类似内容,所以你可以摆脱if 并保持身体而不改变任何东西。 (而这些天,我这样做了。)回复await in run:请注意两件事:1. 如果您在run 中有await,那么根据定义run 必须是async 函数,这意味着它返回一个承诺,这意味着您正在向某事返回一个承诺([cont'd] 中的代码
【解决方案2】:

您需要使用一个变量来跟踪“完成度”,然后在循环的每次迭代中对其进行测试。如果完成 == true 则返回。

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

【讨论】:

    【解决方案3】:

    我不确定,但可能是你想要的:

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

    【讨论】:

      【解决方案4】:

      我知道这是一个老问题,我还是想发布我的方法。这样你就不必处理 T. J. Crowder 解释的 0 技巧了。

      var keepGoing = true;
      
      function myLoop() {
          // ... Do something ...
      
          if(keepGoing) {
              setTimeout(myLoop, 1000);
          }
      }
      
      function startLoop() {
          keepGoing = true;
          myLoop();
      }
      
      function stopLoop() {
          keepGoing = false;
      }
      

      【讨论】:

      • 我知道这是一个旧答案,但是如果您在1000 超时内stopLoopstartLoop 会发生什么?您现在不会运行两个循环,都检查keepGoing 是否为真?
      • 没错,@Mirror318。 setTimeout 返回一个处理程序。应使用clearTimeout 清除该处理程序。
      • 这可行,但取消计时器本身更准确,因为您不必等到循环完成,在您的示例中,这可能需要整整一秒。
      【解决方案5】:

      由于这是用 extjs 标签标记的,因此可能值得查看 extjs 方法:http://docs.sencha.com/extjs/6.2.0/classic/Ext.Function.html#method-interval

      这很像 setInterval,但也负责范围,并允许传递参数:

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

      当你想停止时,你可以使用clearInterval 停止它

      clearInterval(bgPositionTimer);
      

      一个示例用例是:

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

      【讨论】:

        【解决方案6】:

        处理超时循环的最简单方法

        function myFunc (terminator = false) {
            if(terminator) {
                clearTimeout(timeOutVar);
            } else {
                // do something
                timeOutVar = setTimeout(function(){myFunc();}, 1000);
            }
        }   
        myFunc(true); //  -> start loop
        myFunc(false); //  -> end loop
        

        【讨论】:

        • 如果我错了请纠正我,但不是逻辑颠倒了吗?
        【解决方案7】:
        var myVar = null;
        
        if(myVar)
           clearTimeout(myVar);
        
        myVar = setTimeout(function(){ alert("Hello"); }, 3000);
        

        【讨论】:

        • 这可能会回答这个问题。但是,仅代码的答案不如记录代码或详细解释为什么此代码是问题的解决方案的答案有用。
        【解决方案8】:

        如果您想从函数内部停止循环,请尝试这样的操作:

        let timer = setInterval(function(){
          // Have some code to do something
        
          if(/*someStopCondition*/){ 
            clearInterval(timer)
          }
        },1000);
        
        

        您也可以将其包装在另一个函数中,只需确保您有一个计时器变量并使用 clearInterval(theTimerVariable) 来停止循环

        【讨论】:

          【解决方案9】:

          在最佳答案中,我认为 if (timer) 语句被错误地放置在 stop() 函数调用中。它应该放在run() 函数调用中,如if (timer) timer = setTimeout(run, 200)。这可以防止将来的 setTimeout 语句在调用 stop() 之后立即运行。

          编辑 2: 对于同步函数调用,最佳答案是正确的。如果您想进行异步函数调用,请改用我的。

          以下是我认为正确方法的示例(如果我错了,请纠正我,因为我还没有测试过):

          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
          

          编辑 1: 这已经过测试并按预期工作。

          【讨论】:

          • “在最佳答案中,我认为 if (timer) 语句被错误地放在了 stop() 函数调用中” 不,它在正确的位置。它在那里,所以当您调用 stop 时,如果计时器正在运行,它会停止计时器。正如我 explained here when you asked about it 一样,您在致电 clearTimeout 之前没有必须进行检查(现在我没有),但这并不正确。
          • @T.J.Crowder 感谢您的回复。您是正确的,如果在 stop() 函数中放置或删除 if (timer) 语句不会更改任何内容。但是,我坚持我的说法,即 if 语句仍然需要放在 run() 函数中,如 if (timer) timer = setTimeout(run, 200);
          • 要解释这一点,请考虑代码中的情况,即在执行Ext.get()...c = 0; 之间的任何代码时调用stop()。在这种情况下,clearTimeout 将清除先前设置的timer,而不是在c = 0; 行之后立即执行的那个,这将导致即使您调用了stop(),您的循环也会继续。
          • 如果Ext.get()...c=0; 之间的行改为await async function 可能不会立即完成执行,这当然更有可能发生。我在上面的回答中给出的代码涵盖了这种边缘情况并且不会让它发生。
          • @T.J.Crowder 好的,考虑更多,是的,您是对的,感谢您的讨论和澄清! :)
          【解决方案10】:

          当任务完成并且您可以显示任务(在您的情况下为图像)时,下次刷新时不要发送 javascript。如果您的服务器使用的是 PHP。

          <?php if (!$taskCompleted) { ?>
          <script language="javascript">
          setTimeout(function(){
             window.location.reload(1);
          }, 5000);
          </script>
          <?php } ?>
          

          【讨论】:

            猜你喜欢
            • 1970-01-01
            • 1970-01-01
            • 1970-01-01
            • 1970-01-01
            • 2014-12-31
            • 1970-01-01
            • 2018-05-13
            • 1970-01-01
            • 1970-01-01
            相关资源
            最近更新 更多