javascript: 暂停 setTimeout();

Posted

技术标签:

【中文标题】javascript: 暂停 setTimeout();【英文标题】:javascript: pause setTimeout(); 【发布时间】:2011-04-27 12:52:10 【问题描述】:

如果我有一个通过var t = setTimeout("dosomething()", 5000) 设置的活动超时运行,

是否有暂停和恢复它?


有什么方法可以获取当前超时的剩余时间? 还是我必须在一个变量中,当设置超时时,存储当前时间,然后我们暂停,获取现在和那时之间的差异?

【问题讨论】:

对于那些想知道的人来说,暂停是为了例如:一个 div 设置为在 5 秒内消失,在 3 秒时(剩下 2 秒)用户将鼠标悬停在 div 上,你暂停超时,一旦用户将鼠标从 div 上移开,您恢复它,2 秒后它就会消失。 【参考方案1】:

您可以像这样包装window.setTimeout,我认为这与您在问题中的建议相似:

var Timer = function(callback, delay) 
    var timerId, start, remaining = delay;

    this.pause = function() 
        window.clearTimeout(timerId);
        timerId = null;
        remaining -= Date.now() - start;
    ;

    this.resume = function() 
        if (timerId) 
            return;
        

        start = Date.now();
        timerId = window.setTimeout(callback, remaining);
    ;

    this.resume();
;

var timer = new Timer(function() 
    alert("Done!");
, 1000);

timer.pause();
// Do some stuff...
timer.resume();

【讨论】:

@yckart:回滚,抱歉。这是一个很好的补充,只是在 Internet Explorer setTimeout() 添加附加参数。 如果你这样做timer.resume(); timer.resume();,你最终会同时出现两个超时。这就是为什么你想要么先clearTimeout(timerId),要么在简历一开始就短路if (timerId) return; 嘿,我喜欢这个答案,但我不得不将:var timerId, start, remaining; 拉到 Class 范围之外,然后在里面添加 remaining = delay; 以捕获参数。像魅力一样工作! @Josh979:你真的不需要这样做,这样做是个坏主意,因为它暴露了应该是内部的变量。也许您已将代码粘贴到块中(例如,在 if (blah) ... 中)或其他内容? 由于某种原因,初始化后我只运行一次。【参考方案2】:

这样的事情应该可以解决问题。

function Timer(fn, countdown) 
    var ident, complete = false;

    function _time_diff(date1, date2) 
        return date2 ? date2 - date1 : new Date().getTime() - date1;
    

    function cancel() 
        clearTimeout(ident);
    

    function pause() 
        clearTimeout(ident);
        total_time_run = _time_diff(start_time);
        complete = total_time_run >= countdown;
    

    function resume() 
        ident = complete ? -1 : setTimeout(fn, countdown - total_time_run);
    

    var start_time = new Date().getTime();
    ident = setTimeout(fn, countdown);

    return  cancel: cancel, pause: pause, resume: resume ;

【讨论】:

我将+new Date() 更改为new Date().getTime(),因为它更快:jsperf.com/date-vs-gettime 最好的,爸爸!【参考方案3】:

没有。您需要取消它 (clearTimeout),测量自启动以来的时间,然后使用新时间重新启动它。

【讨论】:

【参考方案4】:

Tim Downs answer 的略微修改版本。但是,由于 Tim rolled back 我的编辑,我必须自己回答这个问题。我的解决方案可以使用额外的 arguments 作为第三个 (3, 4, 5...) 参数并清除计时器:

function Timer(callback, delay) 
    var args = arguments,
        self = this,
        timer, start;

    this.clear = function () 
        clearTimeout(timer);
    ;

    this.pause = function () 
        this.clear();
        delay -= new Date() - start;
    ;

    this.resume = function () 
        start = new Date();
        timer = setTimeout(function () 
            callback.apply(self, Array.prototype.slice.call(args, 2, args.length));
        , delay);
    ;

    this.resume();

正如 Tim 所提到的,IE lt 9 中没有额外的参数,但是我进行了一些工作,以便它也可以在 oldIE 中使用。

用法:new Timer(Function, Number, arg1, arg2, arg3...)

function callback(foo, bar) 
    console.log(foo); // "foo"
    console.log(bar); // "bar"


var timer = new Timer(callback, 1000, "foo", "bar");

timer.pause();
document.onclick = timer.resume;

【讨论】:

【参考方案5】:

“暂停”和“恢复”在setTimeout 的上下文中没有多大意义,这是一个一次性的事情。您可能希望暂停setTimeout 调用的链式系列,在这种情况下,不要安排下一个调用(也许通过clearTimeout 取消未完成的调用,如下所示)。但是setTimeout 本身不会循环,没有什么可以暂停和恢复的。

如果你的意思是setInterval 那么不行,你不能暂停它,你只能取消它(clearInterval)然后重新安排它。所有这些的详细信息都在规范的Timers section 中。

// Setting
var t = setInterval(doSomething, 1000);

// Pausing (which is really stopping)
clearInterval(t);
t = 0;

// Resuming (which is really just setting again)
t = setInterval(doSomething, 1000);

【讨论】:

【参考方案6】:

Timeout 很容易找到解决方案,但 Interval 有点棘手。

我想出了以下两个类来解决这个问题:

function PauseableTimeout(func, delay)
    this.func = func;

    var _now = new Date().getTime();
    this.triggerTime = _now + delay;

    this.t = window.setTimeout(this.func,delay);

    this.paused_timeLeft = 0;

    this.getTimeLeft = function()
        var now = new Date();

        return this.triggerTime - now;
    

    this.pause = function()
        this.paused_timeLeft = this.getTimeLeft();

        window.clearTimeout(this.t);
        this.t = null;
    

    this.resume = function()
        if (this.t == null)
            this.t = window.setTimeout(this.func, this.paused_timeLeft);
        
    

    this.clearTimeout = function() window.clearTimeout(this.t);


function PauseableInterval(func, delay)
    this.func = func;
    this.delay = delay;

    this.triggerSetAt = new Date().getTime();
    this.triggerTime = this.triggerSetAt + this.delay;

    this.i = window.setInterval(this.func, this.delay);

    this.t_restart = null;

    this.paused_timeLeft = 0;

    this.getTimeLeft = function()
        var now = new Date();
        return this.delay - ((now - this.triggerSetAt) % this.delay);
    

    this.pause = function()
        this.paused_timeLeft = this.getTimeLeft();
        window.clearInterval(this.i);
        this.i = null;
    

    this.restart = function(sender)
        sender.i = window.setInterval(sender.func, sender.delay);
    

    this.resume = function()
        if (this.i == null)
            this.i = window.setTimeout(this.restart, this.paused_timeLeft, this);
        
    

    this.clearInterval = function() window.clearInterval(this.i);

这些可以这样实现:

var pt_hey = new PauseableTimeout(function()
    alert("hello");
, 2000);

window.setTimeout(function()
    pt_hey.pause();
, 1000);

window.setTimeout("pt_hey.start()", 2000);

此示例将设置一个可暂停的超时 (pt_hey),它计划在两秒后发出“嘿”警报。另一个超时在一秒钟后暂停 pt_hey。第三个超时在两秒后恢复 pt_hey。 pt_hey 运行一秒钟,暂停一秒钟,然后恢复运行。 pt_hey 三秒后触发。

现在是更棘手的间隔

var pi_hey = new PauseableInterval(function()
    console.log("hello world");
, 2000);

window.setTimeout("pi_hey.pause()", 5000);

window.setTimeout("pi_hey.resume()", 6000);

这个例子设置了一个可暂停的间隔(pi_hey),每两秒在控制台中写入“hello world”。五秒后超时暂停 pi_hey。另一个超时将在 6 秒后恢复 pi_hey。所以pi_hey会触发两次,运行一秒,暂停一秒,运行一秒,然后每2秒继续触发一次。

其他功能

clearTimeout()clearInterval()

pt_hey.clearTimeout();pi_hey.clearInterval(); 是清除超时和间隔的简单方法。

getTimeLeft()

pt_hey.getTimeLeft();pi_hey.getTimeLeft(); 将返回多少毫秒,直到安排下一个触发器发生。

【讨论】:

你能解释一下你的想法吗,为什么我们需要一个复杂的Class来暂停setInterval?我认为一个简单的if(!true) return; 就可以解决问题,还是我错了? 我这样做是为了让您可以从字面上暂停间隔,而不是在触发时跳过呼叫。如果在游戏中,每 60 秒释放一次加电,而我在即将触发之前暂停游戏,使用您的方法,我将不得不再等待一分钟才能再次加电。这并不是真正的暂停,这只是忽略了一个呼叫。相反,我的方法实际上是暂停,因此,就游戏而言,加电是“准时”释放的。【参考方案7】:

/复活

使用 Class-y 语法糖的 ES6 版本?

(稍作修改:添加了 start())

class Timer 
  constructor(callback, delay) 
    this.callback = callback
    this.remainingTime = delay
    this.startTime
    this.timerId
  

  pause() 
    clearTimeout(this.timerId)
    this.remainingTime -= new Date() - this.startTime
  

  resume() 
    this.startTime = new Date()
    clearTimeout(this.timerId)
    this.timerId = setTimeout(this.callback, this.remainingTime)
  

  start() 
    this.timerId = setTimeout(this.callback, this.remainingTime)
  


// supporting code
const pauseButton = document.getElementById('timer-pause')
const resumeButton = document.getElementById('timer-resume')
const startButton = document.getElementById('timer-start')

const timer = new Timer(() => 
  console.log('called');
  document.getElementById('change-me').classList.add('wow')
, 3000)

pauseButton.addEventListener('click', timer.pause.bind(timer))
resumeButton.addEventListener('click', timer.resume.bind(timer))
startButton.addEventListener('click', timer.start.bind(timer))
<!doctype html>
<html>
<head>
  <title>Traditional HTML Document. ZZz...</title>
  <style type="text/css">
    .wow  color: blue; font-family: Tahoma, sans-serif; font-size: 1em; 
  </style>
</head>
<body>
  <h1>DOM &amp; javascript</h1>

  <div id="change-me">I'm going to repaint my life, wait and see.</div>

  <button id="timer-start">Start!</button>
  <button id="timer-pause">Pause!</button>
  <button id="timer-resume">Resume!</button>
</body>
</html>

【讨论】:

【参考方案8】:

我需要计算已用时间和剩余时间以显示进度条。使用公认的答案并不容易。对于这个任务,'setInterval' 比 'setTimeout' 好。所以,我创建了这个可以在任何项目中使用的 Timer 类。

https://jsfiddle.net/ashraffayad/t0mmv853/

'use strict';


    //Constructor
    var Timer = function(cb, delay) 
      this.cb = cb;
      this.delay = delay;
      this.elapsed = 0;
      this.remaining = this.delay - self.elapsed;
    ;

    console.log(Timer);

    Timer.prototype = function() 
      var _start = function(x, y) 
          var self = this;
          if (self.elapsed < self.delay) 
            clearInterval(self.interval);
            self.interval = setInterval(function() 
              self.elapsed += 50;
              self.remaining = self.delay - self.elapsed;
              console.log('elapsed: ' + self.elapsed, 
                          'remaining: ' + self.remaining, 
                          'delay: ' + self.delay);
              if (self.elapsed >= self.delay) 
                clearInterval(self.interval);
                self.cb();
              
            , 50);
          
        ,
        _pause = function() 
          var self = this;
          clearInterval(self.interval);
        ,
        _restart = function() 
          var self = this;
          self.elapsed = 0;
          console.log(self);
          clearInterval(self.interval);
          self.start();
        ;

      //public member definitions
      return 
        start: _start,
        pause: _pause,
        restart: _restart
      ;
    ();


    // - - - - - - - - how to use this class

    var restartBtn = document.getElementById('restart');
    var pauseBtn = document.getElementById('pause');
    var startBtn = document.getElementById('start');

    var timer = new Timer(function() 
      console.log('Done!');
    , 2000);

    restartBtn.addEventListener('click', function(e) 
      timer.restart();
    );
    pauseBtn.addEventListener('click', function(e) 
      timer.pause();
    );
    startBtn.addEventListener('click', function(e) 
      timer.start();
    );

【讨论】:

【参考方案9】:

基于评分最高的答案的打字稿实现

/** Represents the `setTimeout` with an ability to perform pause/resume actions */
export class Timer 
    private _start: Date;
    private _remaining: number;
    private _durationTimeoutId?: NodeJS.Timeout;
    private _callback: (...args: any[]) => void;
    private _done = false;
    get done () 
        return this._done;
    

    constructor(callback: (...args: any[]) => void, ms = 0) 
        this._callback = () => 
            callback();
            this._done = true;
        ;
        this._remaining = ms;
        this.resume();
    

    /** pauses the timer */
    pause(): Timer 
        if (this._durationTimeoutId && !this._done) 
            this._clearTimeoutRef();
            this._remaining -= new Date().getTime() - this._start.getTime();
        
        return this;
    

    /** resumes the timer */
    resume(): Timer 
        if (!this._durationTimeoutId && !this._done) 
            this._start = new Date;
            this._durationTimeoutId = setTimeout(this._callback, this._remaining);
        
        return this;
    

    /** 
     * clears the timeout and marks it as done. 
     * 
     * After called, the timeout will not resume
     */
    clearTimeout() 
        this._clearTimeoutRef();
        this._done = true;
    

    private _clearTimeoutRef() 
        if (this._durationTimeoutId) 
            clearTimeout(this._durationTimeoutId);
            this._durationTimeoutId = undefined;
        
    


【讨论】:

【参考方案10】:

您可以查看clearTimeout()

或暂停取决于在满足特定条件时设置的全局变量。就像按下了一个按钮。

  <button onclick="myBool = true" > pauseTimeout </button>

  <script>
  var myBool = false;

  var t = setTimeout(function() if (!mybool) dosomething(), 5000);
  </script>

【讨论】:

【参考方案11】:

你也可以用事件来实现它。

您无需计算时间差,而是开始和停止收听在后台持续运行的“tick”事件:

var Slideshow = 

  _create: function()                  
    this.timer = window.setInterval(function()
      $(window).trigger('timer:tick'); , 8000);
  ,

  play: function()            
    $(window).bind('timer:tick', function()
      // stuff
    );       
  ,

  pause: function()        
    $(window).unbind('timer:tick');
  

;

【讨论】:

【参考方案12】:

如果您仍然使用 jquery,请查看 $.doTimeout 插件。这件事是对 setTimeout 的巨大改进,包括让您使用您指定的单个字符串 id 跟踪您的超时,并且每次设置它都不会改变,并实现简单的取消、轮询循环和去抖动,以及更多的。我最常用的 jquery 插件之一。

不幸的是,它不支持开箱即用的暂停/恢复。为此,您需要包装或扩展 $.doTimeout,大概类似于接受的答案。

【讨论】:

我希望 doTimeout 会暂停/恢复,但在查看完整文档、循环示例甚至源代码时我没有看到它。我能看到的最接近暂停的是取消,但是我必须再次使用函数重新创建计时器。我错过了什么吗? 抱歉让你走错了路。我已经从我的回答中消除了这种不准确之处。【参考方案13】:

我需要能够暂停 setTimeout() 以获得类似幻灯片的功能。

这是我自己实现的可暂停计时器。它集成了在 Tim Down 的回答中看到的 cmets,例如更好的暂停(内核的评论)和一种原型设计形式(Umur Gedik 的评论)。

function Timer( callback, delay ) 

    /** Get access to this object by value **/
    var self = this;



    /********************* PROPERTIES *********************/
    this.delay = delay;
    this.callback = callback;
    this.starttime;// = ;
    this.timerID = null;


    /********************* METHODS *********************/

    /**
     * Pause
     */
    this.pause = function() 
        /** If the timer has already been paused, return **/
        if ( self.timerID == null ) 
            console.log( 'Timer has been paused already.' );
            return;
        

        /** Pause the timer **/
        window.clearTimeout( self.timerID );
        self.timerID = null;    // this is how we keep track of the timer having beem cleared

        /** Calculate the new delay for when we'll resume **/
        self.delay = self.starttime + self.delay - new Date().getTime();
        console.log( 'Paused the timer. Time left:', self.delay );
    


    /**
     * Resume
     */
    this.resume = function() 
        self.starttime = new Date().getTime();
        self.timerID = window.setTimeout( self.callback, self.delay );
        console.log( 'Resuming the timer. Time left:', self.delay );
    


    /********************* CONSTRUCTOR METHOD *********************/

    /**
     * Private constructor
     * Not a language construct.
     * Mind var to keep the function private and () to execute it right away.
     */
    var __construct = function() 
        self.starttime = new Date().getTime();
        self.timerID = window.setTimeout( self.callback, self.delay )
    ();    /* END __construct */

   /* END Timer */

例子:

var timer = new Timer( function() console.log( 'hey! this is a timer!' ); , 10000 );
timer.pause();

要测试代码,请使用timer.resume()timer.pause() 几次,然后检查还剩多少时间。 (确保您的控制台已打开。)

使用此对象代替 setTimeout() 就像将 timerID = setTimeout( mycallback, 1000) 替换为 timer = new Timer( mycallback, 1000 ) 一样简单。然后timer.pause()timer.resume() 可供您使用。

【讨论】:

【参考方案14】:
function delay (ms)     return new Promise(resolve => setTimeout(resolve, s));  

“异步”工作演示在: site zarsoft.info

【讨论】:

【参考方案15】:

您可以执行以下操作以使 setTimeout 在服务器端 (Node.js) 上可暂停

const PauseableTimeout = function(callback, delay) 
    var timerId, start, remaining = delay;

    this.pause = function() 
        global.clearTimeout(timerId);
        remaining -= Date.now() - start;
    ;

    this.resume = function() 
        start = Date.now();
        global.clearTimeout(timerId);
        timerId = global.setTimeout(callback, remaining);
    ;

    this.resume();
;

你可以检查如下

var timer = new PauseableTimeout(function() 
    console.log("Done!");
, 3000);
setTimeout(()=>
    timer.pause();
    console.log("setTimeout paused");
,1000);

setTimeout(()=>
    console.log("setTimeout time complete");
,3000)

setTimeout(()=>
    timer.resume();
    console.log("setTimeout resume again");
,5000)

【讨论】:

【参考方案16】:

如果有人想要尊敬的@SeanVieira here 共享的 TypeScript 版本,你可以使用这个:

    public timer(fn: (...args: any[]) => void, countdown: number):  onCancel: () => void, onPause: () => void, onResume: () => void  
        let ident: NodeJS.Timeout | number;
        let complete = false;
        let totalTimeRun: number;
        const onTimeDiff = (date1: number, date2: number) => 
            return date2 ? date2 - date1 : new Date().getTime() - date1;
        ;

        const handlers = 
            onCancel: () => 
                clearTimeout(ident as NodeJS.Timeout);
            ,
            onPause: () => 
                clearTimeout(ident as NodeJS.Timeout);
                totalTimeRun = onTimeDiff(startTime, null);
                complete = totalTimeRun >= countdown;
            ,
            onResume: () => 
                ident = complete ? -1 : setTimeout(fn, countdown - totalTimeRun);
            
        ;

        const startTime = new Date().getTime();
        ident = setTimeout(fn, countdown);

        return handlers;
    

【讨论】:

【参考方案17】:

我认为您找不到比 clearTimeout 更好的东西了。无论如何,您可以稍后再安排一次超时,而不是“恢复”它。

【讨论】:

【参考方案18】:

如果您要隐藏多个 div,您可以使用 setInterval 和多个循环来执行以下操作:

<div id="div1">1</div><div id="div2">2</div>
<div id="div3">3</div><div id="div4">4</div>
<script>
    function hideDiv(elm)
        var interval,
            unit = 1000,
            cycle = 5,
            hide = function()
                interval = setInterval(function()
                    if(--cycle === 0)
                        elm.style.display = 'none';
                        clearInterval(interval);
                    
                    elm.setAttribute('data-cycle', cycle);
                    elm.innerHTML += '*';
                , unit);
            ;
        elm.onmouseover = function()
            clearInterval(interval);
        ;
        elm.onmouseout = function()
            hide();
        ;
        hide();
    
    function hideDivs(ids)
        var id;
        while(id = ids.pop())
            hideDiv(document.getElementById(id));
        
    
    hideDivs(['div1','div2','div3','div4']);
</script>

【讨论】:

以上是关于javascript: 暂停 setTimeout();的主要内容,如果未能解决你的问题,请参考以下文章

javascript setTimeout 和递归函数执行流程

在 Javascript 中的 while 循环内创建暂停

在循环中暂停并重新启动 setTimeout

JavaScript 计时事件

Postman Collection Run 确实会暂停 setTimeout 调用

javascript中的延迟/睡眠?