javascript: pause setTimeout ();

119

Jeśli mam aktywny limit czasu, który został ustawiony var t = setTimeout("dosomething()", 5000) ,

Czy w ogóle można go wstrzymać i wznowić?


Czy istnieje sposób, aby uzyskać czas pozostały w bieżącym limicie czasu?
czy też muszę w zmiennej, gdy ustawiony jest limit czasu, zapamiętać aktualny czas, a następnie zatrzymujemy się, uzyskujemy różnicę między teraz a potem?

Hailwood
źródło
1
Dla tych, którzy się zastanawiają, pauza jest na przykład: element div jest ustawiony na zniknięcie za 5 sekund, po 3 sekundach (więc pozostały 2 sekundy) użytkownik przesuwa mysz nad div, wstrzymujesz limit czasu, gdy użytkownik wyłączy się z div wznowisz go, 2 sekundy później znika.
Hailwood

Odpowiedzi:

260

Mógłbyś zawinąć w window.setTimeoutten sposób, co moim zdaniem jest podobne do tego, co sugerowałeś w pytaniu:

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
źródło
3
@yckart: Wycofane, przepraszam. To dobry dodatek, poza tym, że dodanie dodatkowych parametrów do setTimeout()nie działa w Internet Explorerze <= 9.
Tim Down
4
Jeśli to zrobisz timer.resume(); timer.resume();, skończysz z dwoma przekroczeniami czasu równolegle. Dlatego chciałbyś clearTimeout(timerId)najpierw zrobić if (timerId) return;zwarcie na samym początku wznowienia.
kernel
2
Hej, podobała mi się ta odpowiedź, ale musiałem wyciągnąć: var timerId, start, remaining;poza zakresem Class i dodać z remaining = delay;powrotem do środka, aby złapać parametr. Działa jak urok!
phillihp
1
@ Josh979: Naprawdę nie musisz tego robić i jest to zły pomysł, ponieważ ujawnia zmienne, które powinny być wewnętrzne. Może wkleiłeś kod wewnątrz bloku (np. Wewnątrz if (blah) { ... }) czy coś?
Tim Down
1
Z jakiegoś powodu uruchamia się to tylko raz po zainicjowaniu.
peterxz
17

Coś takiego powinno załatwić sprawę.

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
źródło
Zmieniłem +new Date()na, new Date().getTime()ponieważ jest szybszy: jsperf.com/date-vs-gettime
yckart
9

Nie. Będziesz musiał to anulować ( clearTimeout), zmierzyć czas od rozpoczęcia i ponownie uruchomić z nowym czasem.

RoToRa
źródło
7

Nieco zmodyfikowana wersja Tim Downs odpowiedź . Jednak odkąd Tim wycofał moją zmianę, muszę sam na to odpowiedzieć. Moje rozwiązanie umożliwia użycie dodatkowego parametru argumentsjako trzeciego (3, 4, 5 ...) i wyczyszczenie timera:

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

Jak wspomniał Tim, dodatkowe parametry nie są dostępne w programie IE lt 9, jednak popracowałem trochę, aby to zadziałałooldIE również w programie.

Stosowanie: 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
źródło
6

„Wstrzymaj” i „Wznów” nie mają większego sensu w kontekście setTimeout, co jest rzeczą jednorazową . Czy masz na myśli setInterval? Jeśli tak, nie, nie możesz go wstrzymać, możesz go tylko anulować ( clearInterval), a następnie ponownie zaplanować. Szczegóły tego wszystkiego w sekcji Timery specyfikacji.

// 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
źródło
16
W kontekście setTimeout wstrzymanie i wznowienie nadal mają sens.
dbkaplun
6

Timeout był dość łatwy do znalezienia rozwiązania, ale Interval był nieco trudniejszy.

Wymyśliłem następujące dwie klasy, aby rozwiązać ten problem:

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

Można je wdrożyć jako takie:

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

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

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

Ten przykład ustawi wstrzymany limit czasu (pt_hey), który jest zaplanowany do ostrzeżenia „hej” po dwóch sekundach. Kolejny limit czasu wstrzymuje pt_hey po jednej sekundzie. Trzeci limit czasu zostaje wznowiony pt_hey po dwóch sekundach. pt_hey działa przez jedną sekundę, zatrzymuje się na jedną sekundę, a następnie wznawia działanie. pt_hey uruchamia się po trzech sekundach.

Teraz czas na trudniejsze interwały

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

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

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

W tym przykładzie ustawia się przerywany interwał (pi_hey), aby co dwie sekundy pisać w konsoli „hello world”. Limit czasu zatrzymuje się pi_hey po pięciu sekundach. Po sześciu sekundach zostanie wznowiony kolejny limit czasu pi_hey. Więc pi_hey uruchomi się dwukrotnie, uruchomi się na jedną sekundę, zatrzyma się na jedną sekundę, uruchomi się na jedną sekundę, a następnie będzie kontynuował wyzwalanie co 2 sekundy.

INNE FUNKCJE

  • clearTimeout () i clearInterval ()

    pt_hey.clearTimeout();i pi_hey.clearInterval();służyć jako łatwy sposób na wyczyszczenie limitów czasu i przerw.

  • getTimeLeft ()

    pt_hey.getTimeLeft();i pi_hey.getTimeLeft();zwróci liczbę milisekund do następnego wyzwalacza.

TheCrzyMan
źródło
Czy możesz wyjaśnić swoje przemyślenia, dlaczego potrzebujemy złożonej klasy, aby wstrzymać setInterval? Myślę, że prosty if(!true) return;wystarczy, czy się mylę?
yckart
2
Zrobiłem to tak, że możesz dosłownie wstrzymać interwał, zamiast po prostu pomijać połączenie, gdy się uruchomi. Jeśli w grze doładowanie jest uwalniane co 60 sekund, a ja wstrzymuję grę tuż przed jej uruchomieniem, używając Twojej metody, będę musiał poczekać kolejną minutę na kolejne wzmocnienie. To nie jest naprawdę wstrzymywanie, to po prostu ignorowanie połączenia. Zamiast tego Moja metoda faktycznie wstrzymuje, a zatem power-up jest uwalniany „na czas” w odniesieniu do gry.
TheCrzyMan
2

Musiałem obliczyć czas, który upłynął, i pozostały czas, aby wyświetlić pasek postępu. Nie było łatwo skorzystać z zaakceptowanej odpowiedzi. „setInterval” jest lepsze niż „setTimeout” dla tego zadania. Stworzyłem więc klasę Timer, której możesz użyć w dowolnym projekcie.

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
źródło
2

/wskrzesić

Wersja ES6 wykorzystująca cukier syntaktyczny klasy y 💋

(nieznacznie zmodyfikowano: dodano początek ())

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
źródło
1

Możesz zajrzeć do clearTimeout ()

lub wstrzymać w zależności od zmiennej globalnej, która jest ustawiana po spełnieniu określonego warunku. Jak przycisk jest wciśnięty.

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

  <script>
  var myBool = false;

  var t = setTimeout(function() {if (!mybool) {dosomething()}}, 5000);
  </script>
John Hartsock
źródło
1

Możesz to również zaimplementować za pomocą wydarzeń.

Zamiast obliczać różnicę czasu, zaczynasz i zatrzymujesz nasłuchiwanie zdarzenia „tick”, które działa w tle:

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
źródło
1

Jeśli mimo wszystko używasz jquery, sprawdź $ .doTimeout wtyczkę . Jest to ogromna poprawa w stosunku do setTimeout, w tym umożliwienie śledzenia limitów czasu za pomocą jednego identyfikatora ciągu, który określisz i który nie zmienia się za każdym razem, gdy go ustawisz, i zaimplementuj łatwe anulowanie, pętle odpytywania i podbicie, oraz więcej. Jedna z moich najczęściej używanych wtyczek jquery.

Niestety nie obsługuje wstrzymywania / wznawiania po wyjęciu z pudełka. W tym celu należałoby zawinąć lub rozszerzyć $ .doTimeout, prawdopodobnie podobnie do zaakceptowanej odpowiedzi.

Ben Roberts
źródło
Miałem nadzieję, że funkcja doTimeout zatrzyma / wznowi, ale nie widzę tego, patrząc na pełną dokumentację, przykłady zapętlenia, a nawet źródła. Najbliższa pauza, jaką widziałem, to anulowanie, ale wtedy musiałbym ponownie utworzyć timer z funkcją. Przegapiłem coś?
ericslaw
Przepraszam, że poprowadziłem cię złą ścieżką. Usunąłem tę nieścisłość z mojej odpowiedzi.
Ben Roberts
1

Musiałem mieć możliwość wstrzymania setTimeout () dla funkcji podobnej do pokazu slajdów.

Oto moja własna implementacja timera z możliwością wstrzymania. Integruje komentarze widoczne w odpowiedzi Tima Downa, takie jak lepsza pauza (komentarz jądra) i forma prototypowania (komentarz Umura Gedika).

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

Przykład:

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

Aby przetestować kod, użyj timer.resume()itimer.pause() kilka razy i sprawdź, ile czasu zostało. (Upewnij się, że konsola jest otwarta).

Używanie tego obiektu zamiast setTimeout () jest tak proste, jak zastąpienie timerID = setTimeout( mycallback, 1000)go timer = new Timer( mycallback, 1000 ). Wtedy timer.pause()i timer.resume()są dostępne dla Ciebie.

Fabien Snauwaert
źródło
1
function delay (ms)   {  return new Promise(resolve => setTimeout(resolve, s));  }

Działające demo "async" pod adresem: site zarsoft.info

Joe Oliver
źródło
0

Implementacja maszynopisu na podstawie najwyżej ocenionej odpowiedzi

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

}
cuddlemeister
źródło
0

Możesz zrobić jak poniżej, aby wstrzymać setTimeout po stronie serwera (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();
};

i możesz to sprawdzić jak poniżej

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
źródło
-1

Nie sądzę, że znajdziesz coś lepszego niż clearTimeout . W każdym razie zawsze możesz zaplanować inny limit czasu później, zamiast go „wznawiać”.

Nikita Rybak
źródło
-1

Jeśli masz kilka elementów div do ukrycia, możesz użyć setIntervalliczby cykli i kilku cykli, jak w:

<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>
Mic
źródło