javascript: pause setTimeout ();


118

Jika saya memiliki waktu tunggu aktif yang berjalan yang telah diatur var t = setTimeout("dosomething()", 5000) ,

Apakah ada cara untuk menjeda dan melanjutkannya?


Adakah cara untuk mendapatkan waktu yang tersisa pada waktu tunggu saat ini?
atau haruskah saya dalam variabel, ketika waktu tunggu diatur, simpan waktu saat ini, lalu kita jeda, dapatkan perbedaan antara sekarang dan nanti?


1
Bagi mereka yang bertanya-tanya, Jeda adalah untuk misalnya: div disetel untuk menghilang dalam 5 detik, pada 3 detik (jadi 2 detik tersisa) pengguna mengarahkan mouse ke div, Anda menjeda batas waktu, setelah pengguna mengalihkan div Anda melanjutkannya, 2 detik kemudian menghilang.
Hailwood

Jawaban:


261

Anda bisa membungkus window.setTimeoutseperti ini, yang menurut saya mirip dengan apa yang Anda sarankan dalam pertanyaan:

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

3
@yckart: Digulung kembali, maaf. Ini adalah tambahan yang bagus kecuali bahwa menambahkan parameter tambahan setTimeout()tidak berfungsi di Internet Explorer <= 9.
Tim Down

4
Jika Anda melakukannya, timer.resume(); timer.resume();Anda akan memiliki dua batas waktu secara paralel. Itulah mengapa Anda ingin melakukannya clearTimeout(timerId)terlebih dahulu atau melakukan if (timerId) return;korsleting di awal resume.
kernel

2
Hei, saya menyukai jawaban ini, tetapi saya harus menarik: di var timerId, start, remaining;luar lingkup Kelas dan menambahkan remaining = delay;kembali ke dalam untuk menangkap parameter. Bekerja seperti pesona!
phillihp

1
@ Josh979: Anda benar-benar tidak perlu melakukan itu dan itu ide yang buruk untuk melakukannya karena mengekspos variabel yang seharusnya internal. Mungkin Anda telah menempelkan kode di dalam blok (misalnya di dalam if (blah) { ... }) atau sesuatu?
Tim Down

1
Untuk beberapa alasan ini hanya berjalan sekali untuk saya setelah dijalankan.
peterxz

17

Sesuatu seperti ini seharusnya berhasil.

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

Saya berubah +new Date()menjadi new Date().getTime()karena lebih cepat: jsperf.com/date-vs-gettime
yckart

9

Tidak. Anda harus membatalkannya ( clearTimeout), mengukur waktu sejak Anda memulainya dan memulai ulang dengan waktu yang baru.


7

Versi jawaban Tim Downs yang sedikit dimodifikasi . Namun, sejak Tim membatalkan suntingan saya, saya sendiri yang menjawabnya. Solusi saya memungkinkan untuk menggunakan ekstra argumentssebagai parameter ketiga (3, 4, 5 ...) dan untuk menghapus pengatur waktu:

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

Seperti yang disebutkan Tim, parameter tambahan tidak tersedia di IE lt 9, namun saya bekerja sedikit agar dapat berfungsioldIE juga.

Pemakaian: 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;

6

"Jeda" dan "lanjutkan" tidak terlalu masuk akal dalam konteksnya setTimeout, yang merupakan hal satu kali . Apakah yang Anda maksud setInterval? Jika demikian, tidak, Anda tidak dapat menjedanya, Anda hanya dapat membatalkannya ( clearInterval) lalu menjadwalkan ulang lagi. Detail semua ini ada di bagian Timer pada spesifikasi.

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

16
Dalam konteks setTimeout, jeda dan lanjutkan masih masuk akal.
dbkaplun

6

Timeout cukup mudah untuk menemukan solusinya, tetapi Interval sedikit lebih rumit.

Saya datang dengan dua kelas berikut untuk menyelesaikan masalah ini:

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

Ini dapat diimplementasikan seperti:

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

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

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

Contoh ini akan menyetel Timeout yang dapat dijeda (pt_hey) yang dijadwalkan untuk memberi peringatan, "hey" setelah dua detik. Timeout lain menjeda pt_hey setelah satu detik. Timeout ketiga melanjutkan pt_hey setelah dua detik. pt_hey berjalan selama satu detik, berhenti sebentar, kemudian melanjutkan berjalan. pt_hey terpicu setelah tiga detik.

Sekarang untuk interval yang lebih rumit

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

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

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

Contoh ini menyetel Interval yang dapat dijeda (pi_hey) untuk menulis "hello world" di konsol setiap dua detik. Sebuah timeout menjeda pi_hey setelah lima detik. Batas waktu lain melanjutkan pi_hey setelah enam detik. Jadi pi_hey akan memicu dua kali, lari selama satu detik, jeda selama satu detik, lari selama satu detik, lalu lanjutkan memicu setiap 2 detik.

FUNGSI LAINNYA

  • clearTimeout () dan clearInterval ()

    pt_hey.clearTimeout();dan pi_hey.clearInterval();berfungsi sebagai cara mudah untuk menghapus batas waktu dan interval.

  • getTimeLeft ()

    pt_hey.getTimeLeft();dan pi_hey.getTimeLeft();akan mengembalikan berapa milidetik hingga pemicu berikutnya dijadwalkan untuk terjadi.


Bisakah Anda menjelaskan pemikiran Anda, mengapa kita membutuhkan Kelas yang kompleks untuk menjeda setInterval? Saya pikir yang sederhana if(!true) return;akan berhasil, atau apakah saya salah?
yckart

2
Saya membuatnya sehingga Anda benar-benar dapat menjeda interval, alih-alih hanya melewatkan panggilan saat dipicu. Jika, dalam sebuah game, power-up dilepaskan setiap 60 detik, dan saya menjeda game tepat sebelum game tersebut akan dipicu, dengan menggunakan metode Anda, saya harus menunggu satu menit lagi untuk power-up lainnya. Itu tidak benar-benar berhenti, itu hanya mengabaikan panggilan. Sebaliknya, metode saya sebenarnya berhenti, dan oleh karena itu, power-up dilepaskan 'tepat waktu' sehubungan dengan permainan-permainan.
TheCrzyMan

2

Saya perlu menghitung waktu yang telah berlalu dan yang tersisa untuk menunjukkan bilah kemajuan. Tidak mudah menggunakan jawaban yang diterima. 'setInterval' lebih baik daripada 'setTimeout' untuk tugas ini. Jadi, saya membuat kelas Timer ini yang dapat Anda gunakan dalam proyek apa pun.

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

2

/menghidupkan kembali

Versi ES6 menggunakan gula sintaksis Kelas-y 💋

(sedikit dimodifikasi: ditambahkan awal ())

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>


1

Anda bisa melihat ke clearTimeout ()

atau jeda bergantung pada variabel global yang disetel saat kondisi tertentu tercapai. Seperti tombol yang ditekan.

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

  <script>
  var myBool = false;

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

1

Anda juga bisa menerapkannya dengan acara.

Alih-alih menghitung perbedaan waktu, Anda mulai dan berhenti mendengarkan acara 'centang' yang terus berjalan di latar belakang:

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

};

1

Jika Anda menggunakan jquery, lihat $ .doTimeout plugin . Hal ini merupakan peningkatan besar atas setTimeout, termasuk membiarkan Anda melacak time-out Anda dengan id string tunggal yang Anda tentukan dan tidak berubah setiap kali Anda menyetelnya, dan menerapkan pembatalan mudah, polling loop & debouncing, dan lebih. Salah satu plugin jquery saya yang paling banyak digunakan.

Sayangnya, itu tidak mendukung jeda / melanjutkan di luar kotak. Untuk ini, Anda perlu membungkus atau memperpanjang $ .doTimeout, mungkin mirip dengan jawaban yang diterima.


Saya berharap doTimeout akan memiliki jeda / resume, tetapi saya tidak melihatnya ketika melihat dokumentasi lengkap, contoh perulangan, dan bahkan sumber. Yang paling dekat dengan jeda yang bisa saya lihat adalah membatalkan, tetapi kemudian saya harus membuat ulang pengatur waktu dengan fungsi lagi. Apakah saya melewatkan sesuatu?
ericslaw

Maaf membawa Anda ke jalan yang salah. Saya telah menghapus ketidakakuratan itu dari jawaban saya.
Ben Roberts

1

Saya harus bisa menjeda setTimeout () untuk fitur seperti slideshow.

Berikut adalah implementasi saya sendiri dari pengatur waktu yang dapat dijeda. Ini mengintegrasikan komentar yang terlihat pada jawaban Tim Down, seperti jeda yang lebih baik (komentar kernel) dan bentuk prototipe (komentar 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 */

Contoh:

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

Untuk menguji kode, gunakan timer.resume()dantimer.pause() beberapa kali dan periksa berapa banyak waktu yang tersisa. (Pastikan konsol Anda terbuka.)

Menggunakan objek ini sebagai pengganti setTimeout () semudah menggantinya timerID = setTimeout( mycallback, 1000)dengan timer = new Timer( mycallback, 1000 ). Kemudian timer.pause()dan timer.resume()tersedia untuk Anda.



0

Implementasi skrip ketikan berdasarkan jawaban teratas

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

}

0

Anda dapat melakukan seperti di bawah ini untuk membuat setTimeout dapat dijeda di sisi server (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();
};

dan Anda dapat memeriksanya seperti di bawah ini

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)

-1

Saya tidak berpikir Anda akan menemukan sesuatu yang lebih baik dari clearTimeout . Bagaimanapun, Anda selalu dapat menjadwalkan waktu tunggu lain nanti, alih-alih 'melanjutkan'.


-1

Jika Anda memiliki beberapa div untuk disembunyikan, Anda dapat menggunakan setIntervaldan sejumlah siklus untuk melakukan seperti di:

<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>
Dengan menggunakan situs kami, Anda mengakui telah membaca dan memahami Kebijakan Cookie dan Kebijakan Privasi kami.
Licensed under cc by-sa 3.0 with attribution required.