javascript: pause setTimeout ();

119

Si tengo un tiempo de espera activo que se configuró var t = setTimeout("dosomething()", 5000),

¿Hay alguna forma de pausar y reanudar?


¿Hay alguna forma de obtener el tiempo restante del tiempo de espera actual?
¿O tengo que hacerlo en una variable, cuando se establece el tiempo de espera, almacenar la hora actual, luego hacemos una pausa, obtenemos la diferencia entre ahora y entonces?

Granizo
fuente
1
Para aquellos que se preguntan, la pausa es, por ejemplo: un div está configurado para desaparecer en 5 segundos, a los 3 segundos (es decir, quedan 2 segundos) el usuario pasa el mouse sobre el div, usted pausa el tiempo de espera, una vez que el usuario deja el div. lo reanuda, 2 segundos después desaparece.
Hailwood

Respuestas:

260

Podría envolver window.setTimeoutasí, que creo que es similar a lo que sugirió en la pregunta:

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

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

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

    this.resume();
};

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

timer.pause();
// Do some stuff...
timer.resume();
Tim Down
fuente
3
@yckart: Retrocedido, lo siento. Es una buena adición, excepto que agregar parámetros adicionales a setTimeout()no funciona en Internet Explorer <= 9.
Tim Down
4
Si lo hace timer.resume(); timer.resume();, terminará teniendo dos tiempos de espera en paralelo. Es por eso que querrá clearTimeout(timerId)primero o hacer un if (timerId) return;cortocircuito al comienzo del currículum.
kernel
2
Oye, me gustó esta respuesta, pero tuve que tirar: var timerId, start, remaining;fuera del alcance de la Clase y agregar remaining = delay;nuevamente adentro para capturar el parámetro. ¡Aunque funciona como un encanto!
phillihp
1
@ Josh979: Realmente no necesitas hacer eso y es una mala idea hacerlo porque expone variables que deberían ser internas. ¿Quizás ha pegado el código dentro de un bloque (por ejemplo, dentro de un if (blah) { ... }) o algo así?
Tim Down
1
Por alguna razón, esto solo se ejecuta una vez después de ser inicializado.
peterxz
17

Algo como esto debería funcionar.

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 };
}
Sean Vieira
fuente
Cambié +new Date()a new Date().getTime()porque es más rápido: jsperf.com/date-vs-gettime
yckart
9

No. Necesitará cancelarlo ( clearTimeout), medir el tiempo desde que lo inició y reiniciarlo con la nueva hora.

RoToRa
fuente
7

Una versión ligeramente modificada de la respuesta de Tim Downs . Sin embargo, desde que Tim revirtió mi edición, tengo que responder esto yo mismo. Mi solución hace posible usar extra argumentscomo tercer parámetro (3, 4, 5 ...) y borrar el temporizador:

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

Como mencionó Tim, los parámetros adicionales no están disponibles en IE lt 9, sin embargo, trabajé un poco para que funcione enoldIE también.

Uso: 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;
yckart
fuente
6

"Pausa" y "hoja de vida" realmente no tiene mucho sentido en el contexto de setTimeout, que es una sola vez cosa. ¿Te refieres setInterval? Si es así, no, no puede pausarlo, solo puede cancelarlo ( clearInterval) y luego reprogramarlo nuevamente. Detalles de todos estos en la sección Temporizadores de la especificación.

// 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);
TJ Crowder
fuente
16
En el contexto de setTimeout, pausar y reanudar todavía tienen sentido.
dbkaplun
6

El tiempo de espera fue bastante fácil de encontrar una solución, pero el intervalo fue un poco más complicado.

Se me ocurrieron las siguientes dos clases para resolver estos problemas:

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

Estos se pueden implementar como tales:

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

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

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

Este ejemplo establecerá un tiempo de espera pausable (pt_hey) que está programado para alertar, "hey" después de dos segundos. Otro tiempo de espera hace una pausa pt_hey después de un segundo. Un tercer tiempo de espera se reanuda pt_hey después de dos segundos. pt_hey se ejecuta durante un segundo, se detiene durante un segundo y luego se reanuda. pt_hey se activa después de tres segundos.

Ahora para los intervalos más complicados

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

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

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

Este ejemplo establece un intervalo pausable (pi_hey) para escribir "hola mundo" en la consola cada dos segundos. Un tiempo de espera detiene pi_hey después de cinco segundos. Otro tiempo de espera se reanuda pi_hey después de seis segundos. Entonces pi_hey se activará dos veces, se ejecutará durante un segundo, se detendrá durante un segundo, se ejecutará durante un segundo y luego continuará activando cada 2 segundos.

OTRAS FUNCIONES

  • clearTimeout () y clearInterval ()

    pt_hey.clearTimeout(); y pi_hey.clearInterval(); servir como una forma fácil de borrar los tiempos de espera y los intervalos.

  • getTimeLeft ()

    pt_hey.getTimeLeft();y pi_hey.getTimeLeft();devolverá cuántos milisegundos están programados para que ocurra el siguiente disparador.

TheCrzyMan
fuente
¿Puede explicar sus pensamientos, por qué necesitamos una clase compleja para hacer una pausa setInterval? Creo que un simple if(!true) return;servirá, ¿o me equivoco?
yckart
2
Lo hice para que literalmente puedas pausar el intervalo, en lugar de simplemente saltarte una llamada cuando se activa. Si, en un juego, se lanza un power-up cada 60 segundos, y hago una pausa en el juego justo antes de que esté a punto de dispararse, usando su método, tendré que esperar otro minuto para otro power-up. Eso no es realmente una pausa, es solo ignorar una llamada. En cambio, Mi método está en pausa y, por lo tanto, el encendido se lanza "a tiempo" con respecto al juego.
TheCrzyMan
2

Necesitaba calcular el tiempo transcurrido y restante para mostrar una barra de progreso. No fue fácil utilizar la respuesta aceptada. 'setInterval' es mejor que 'setTimeout' para esta tarea. Entonces, creé esta clase Timer que puedes usar en cualquier proyecto.

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();
    });
Ashraf Fayad
fuente
2

/reanimar

Versión ES6 usando azúcar sintáctico Class-y 💋

(ligeramente modificado: inicio agregado ())

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>

jeremiah.trein
fuente
1

Podrías mirar en clearTimeout ()

o pausa dependiendo de una variable global que se establece cuando se alcanza una determinada condición. Como si se presiona un botón.

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

  <script>
  var myBool = false;

  var t = setTimeout(function() {if (!mybool) {dosomething()}}, 5000);
  </script>
John Hartsock
fuente
1

También podría implementarlo con eventos.

En lugar de calcular la diferencia horaria, comienza y deja de escuchar un evento de 'tick' que sigue ejecutándose en segundo plano:

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

};
leal
fuente
1

Si está usando jquery de todos modos, consulte el $ .doTimeout complemento . Esto es una gran mejora con respecto a setTimeout, lo que incluye permitirle realizar un seguimiento de sus tiempos de espera con una única identificación de cadena que especifique y que no cambia cada vez que la configura, e implementar una cancelación fácil, bucles de sondeo y eliminación de rebotes, y más. Uno de mis complementos de jquery más utilizados.

Desafortunadamente, no admite pausa / reanudación de fábrica. Para esto, necesitaría ajustar o extender $ .doTimeout, presumiblemente de manera similar a la respuesta aceptada.

Ben Roberts
fuente
Esperaba que doTimeout tuviera una pausa / reanudación, pero no lo veo cuando miro la documentación completa, los ejemplos de bucle e incluso la fuente. Lo más cercano a la pausa que pude ver es cancelar, pero luego tendría que volver a crear el temporizador con la función. ¿Me he perdido algo?
ericslaw
Perdón por llevarte por el camino equivocado. Eliminé esa inexactitud de mi respuesta.
Ben Roberts
1

Necesitaba poder pausar setTimeout () para una función similar a una presentación de diapositivas.

Aquí está mi propia implementación de un temporizador pausable. Integra los comentarios vistos en la respuesta de Tim Down, como una mejor pausa (comentario del kernel) y una forma de creación de prototipos (comentario de 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 */

Ejemplo:

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

Para probar el código, use timer.resume()ytimer.pause() varias veces y verifique cuánto tiempo queda. (Asegúrese de que su consola esté abierta).

Usar este objeto en lugar de setTimeout () es tan fácil como reemplazarlo timerID = setTimeout( mycallback, 1000)con timer = new Timer( mycallback, 1000 ). Entonces timer.pause()y timer.resume()están disponibles para ti.

Fabien Snauwaert
fuente
1
function delay (ms)   {  return new Promise(resolve => setTimeout(resolve, s));  }

Demostración de trabajo "async" en: sitio zarsoft.info

Joe Oliver
fuente
0

Implementación de mecanografiado basada en la respuesta mejor calificada

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

}
abrazarmeister
fuente
0

Puede hacer lo siguiente para hacer que setTimeout sea pausable en el lado del servidor (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();
};

y puedes comprobarlo de la siguiente manera

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)
Manoj Rana
fuente
-1

No creo que encuentres nada mejor que clearTimeout . De todos modos, siempre puede programar otro tiempo de espera más tarde, en lugar de 'reanudarlo'.

Nikita Rybak
fuente
-1

Si tiene varios divs para ocultar, puede usar una setIntervaly una cantidad de ciclos para hacer como en:

<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>
Micrófono
fuente