javascript: pause setTimeout ();

118

Si j'ai un délai d'attente actif qui a été défini var t = setTimeout("dosomething()", 5000),

Est-il possible de faire une pause et de le reprendre?


Existe-t-il un moyen d'obtenir le temps restant sur le délai d'expiration actuel?
ou dois-je dans une variable, lorsque le délai d'expiration est défini, stocker l'heure actuelle, puis nous nous arrêtons, obtenons la différence entre maintenant et alors?

Hailwood
la source
1
Pour ceux qui se demandent, la pause est pour par exemple: un div est configuré pour disparaître dans 5 secondes, à 3 secondes (donc 2 secondes restantes) l'utilisateur passe la souris sur le div, vous suspendez le délai d'attente, une fois que l'utilisateur souris hors du div vous le reprenez, 2 secondes plus tard, il disparaît.
Hailwood

Réponses:

261

Vous pourriez emballer window.setTimeoutcomme ceci, ce qui, je pense, est similaire à ce que vous suggériez dans la question:

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
la source
3
@yckart: Recul, désolé. C'est un bon ajout, sauf que l'ajout de paramètres supplémentaires à setTimeout()ne fonctionne pas dans Internet Explorer <= 9.
Tim Down
4
Si vous le faites, timer.resume(); timer.resume();vous finirez par avoir deux délais d'attente en parallèle. C'est pourquoi vous voudriez soit d' clearTimeout(timerId)abord, soit faire un if (timerId) return;court-circuit au tout début de la reprise.
noyau du
2
Hé, j'ai aimé cette réponse, mais j'ai dû tirer: en var timerId, start, remaining;dehors de la portée de la classe et ajouter remaining = delay;à l'intérieur pour attraper le paramètre. Fonctionne comme un charme tho!
phillihp
1
@ Josh979: Vous n'avez vraiment pas besoin de faire cela et c'est une mauvaise idée de le faire car cela expose des variables qui devraient être internes. Peut-être avez-vous collé le code dans un bloc (par exemple dans un if (blah) { ... }) ou quelque chose?
Tim Down
1
Pour une raison quelconque, cela ne fonctionne qu'une seule fois pour moi après avoir été initialisé.
peterxz le
17

Quelque chose comme ça devrait faire l'affaire.

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
la source
J'ai changé +new Date()pour new Date().getTime()car il est plus rapide: jsperf.com/date-vs-gettime
yckart
9

Non. Vous devrez l'annuler ( clearTimeout), mesurer le temps écoulé depuis que vous l'avez démarré et le redémarrer avec la nouvelle heure.

RoToRa
la source
7

Une version légèrement modifiée de la réponse de Tim Downs . Cependant, depuis que Tim a annulé ma modification, je dois répondre moi-même. Ma solution permet d'utiliser extra argumentscomme troisième paramètre (3, 4, 5 ...) et d'effacer le timer:

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

Comme Tim l'a mentionné, les paramètres supplémentaires ne sont pas disponibles dans IE lt 9, mais j'ai travaillé un peu pour que cela fonctionne dansoldIE les.

Usage: 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
la source
6

"Pause" et "reprendre" n'ont pas vraiment beaucoup de sens dans le contexte de setTimeout, ce qui est une chose ponctuelle . Voulez-vous dire setInterval? Si tel est le cas, non, vous ne pouvez pas le mettre en pause, vous pouvez seulement l'annuler ( clearInterval) et le reprogrammer à nouveau. Détails de tout cela dans la section Minuteries de la spécification.

// 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
la source
16
Dans le contexte de setTimeout, la pause et la reprise ont toujours du sens.
dbkaplun
6

Le délai d'attente était assez facile pour trouver une solution, mais l'intervalle était un peu plus délicat.

J'ai proposé les deux classes suivantes pour résoudre ces problèmes:

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

Ceux-ci peuvent être mis en œuvre comme tels:

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

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

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

Cet exemple définira un délai d'attente (pt_hey) qui est programmé pour alerter, "hey" après deux secondes. Un autre Timeout met pt_hey en pause après une seconde. Un troisième Timeout reprend pt_hey après deux secondes. pt_hey s'exécute pendant une seconde, fait une pause d'une seconde, puis reprend son exécution. pt_hey se déclenche après trois secondes.

Maintenant pour les intervalles plus délicats

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

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

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

Cet exemple définit un intervalle de pause (pi_hey) pour écrire "hello world" dans la console toutes les deux secondes. Un timeout met pi_hey en pause après cinq secondes. Un autre délai d'expiration reprend pi_hey après six secondes. Ainsi pi_hey se déclenchera deux fois, fonctionnera pendant une seconde, marquera une pause d'une seconde, fonctionnera pendant une seconde, puis continuera à déclencher toutes les 2 secondes.

AUTRES FONCTIONS

  • clearTimeout () et clearInterval ()

    pt_hey.clearTimeout();et pi_hey.clearInterval();servent de moyen facile d'effacer les délais et les intervalles.

  • getTimeLeft ()

    pt_hey.getTimeLeft();et pi_hey.getTimeLeft();renverra le nombre de millisecondes jusqu'à ce que le prochain déclencheur se produise.

TheCrzyMan
la source
Pouvez-vous expliquer vos pensées, pourquoi nous avons besoin d'une classe complexe pour mettre en pause un setInterval? Je pense qu'un simple if(!true) return;fera l'affaire, ou ai-je tort?
yckart
2
Je l'ai fait pour que vous puissiez littéralement mettre l'intervalle en pause, au lieu de simplement sauter un appel lorsqu'il se déclenche. Si, dans un jeu, une mise sous tension est lancée toutes les 60 secondes et que je mets le jeu en pause juste avant qu'il ne soit sur le point de se déclencher, en utilisant votre méthode, je devrai attendre encore une minute pour une autre mise sous tension. Ce n'est pas vraiment une pause, c'est simplement ignorer un appel. Au lieu de cela, Ma méthode est en fait en pause, et par conséquent, la mise sous tension est libérée «à temps» en ce qui concerne le jeu.
TheCrzyMan
2

J'avais besoin de calculer le temps écoulé et restant pour afficher une barre de progression. Ce n'était pas facile d'utiliser la réponse acceptée. «setInterval» est meilleur que «setTimeout» pour cette tâche. J'ai donc créé cette classe Timer que vous pouvez utiliser dans n'importe quel projet.

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
la source
2

/relancer

Version ES6 utilisant le sucre syntaxique Class-y 💋

(légèrement modifié: ajout de 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>

jeremiah.trein
la source
1

Vous pouvez consulter clearTimeout ()

ou pause selon une variable globale qui est définie lorsqu'une certaine condition est atteinte. Comme un bouton est enfoncé.

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

  <script>
  var myBool = false;

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

Vous pouvez également l'implémenter avec des événements.

Au lieu de calculer le décalage horaire, vous démarrez et arrêtez d'écouter un événement 'tick' qui continue de s'exécuter en arrière-plan:

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

};
Meleyal
la source
1

Si vous utilisez de toute façon jquery, consultez le $ .doTimeout plugin . Cette chose est une énorme amélioration par rapport à setTimeout, notamment en vous permettant de suivre vos délais d'expiration avec un seul identifiant de chaîne que vous spécifiez et qui ne change pas à chaque fois que vous le définissez, et implémentez une annulation facile, des boucles d'interrogation et un débouncing, et plus. Un de mes plugins jquery les plus utilisés.

Malheureusement, il ne prend pas en charge la pause / reprise hors de la boîte. Pour cela, vous devrez encapsuler ou étendre $ .doTimeout, vraisemblablement de la même manière que la réponse acceptée.

Ben Roberts
la source
J'espérais que doTimeout aurait une pause / reprise, mais je ne le vois pas en regardant la documentation complète, les exemples de boucle et même la source. Le plus proche de la pause que je pouvais voir est annuler, mais alors je devrais recréer le minuteur avec la fonction à nouveau. Ai-je oublié quelque chose?
ericslaw
Désolé de vous conduire sur la mauvaise voie. J'ai supprimé cette inexactitude de ma réponse.
Ben Roberts
1

J'avais besoin de pouvoir mettre en pause setTimeout () pour la fonction de type diaporama.

Voici ma propre implémentation d'une minuterie en pause. Il intègre des commentaires vus sur la réponse de Tim Down, tels qu'une meilleure pause (commentaire du noyau) et une forme de prototypage (commentaire d'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 */

Exemple:

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

Pour tester le code, utilisez timer.resume()ettimer.pause() plusieurs fois et vérifiez combien de temps il reste. (Assurez-vous que votre console est ouverte.)

Utiliser cet objet à la place de setTimeout () est aussi simple que de le remplacer timerID = setTimeout( mycallback, 1000)par timer = new Timer( mycallback, 1000 ). Alors timer.pause()et timer.resume()sont à votre disposition.

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

Démo de travail "async" sur: site zarsoft.info

Joe Oliver
la source
0

Implémentation typographique basée sur la réponse la mieux notée

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

}
câlin
la source
0

Vous pouvez faire comme ci-dessous pour rendre setTimeout en pause côté serveur (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();
};

et vous pouvez le vérifier comme ci-dessous

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
la source
-1

Je ne pense pas que vous trouverez quelque chose de mieux que clearTimeout . Quoi qu'il en soit, vous pouvez toujours planifier un autre délai d'attente plus tard, au lieu de le «reprendre».

Nikita Rybak
la source
-1

Si vous avez plusieurs div à masquer, vous pouvez utiliser un setIntervalet un certain nombre de cycles pour faire comme dans:

<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>
Micro
la source