menemukan waktu yang tersisa di setTimeout ()?


90

Saya menulis beberapa Javascript yang berinteraksi dengan kode perpustakaan yang tidak saya miliki, dan tidak dapat (secara wajar) ubah. Ini membuat batas waktu Javascript yang digunakan untuk menampilkan pertanyaan berikutnya dalam serangkaian pertanyaan dengan waktu terbatas. Ini bukan kode nyata karena ia dikaburkan di luar harapan. Inilah yang perpustakaan lakukan:

....
// setup a timeout to go to the next question based on user-supplied time
var t = questionTime * 1000
test.currentTimeout = setTimeout( showNextQuestion(questions[i+1]), t );

Saya ingin meletakkan bilah kemajuan di layar yang terisi questionTime * 1000dengan menginterogasi pengatur waktu yang dibuat oleh setTimeout. Satu-satunya masalah adalah, sepertinya tidak ada cara untuk melakukan ini. Apakah ada getTimeoutfungsi yang saya lewatkan? Satu-satunya informasi tentang batas waktu Javascript yang dapat saya temukan hanya terkait dengan pembuatan melalui setTimeout( function, time)dan penghapusan melalui clearTimeout( id ).

Saya mencari fungsi yang mengembalikan waktu yang tersisa sebelum batas waktu diaktifkan, atau waktu yang berlalu setelah batas waktu dipanggil. Kode bar kemajuan saya terlihat seperti ini:

var  timeleft = getTimeout( test.currentTimeout ); // I don't know how to do this
var  $bar = $('.control .bar');
while ( timeleft > 1 ) {
    $bar.width(timeleft / test.defaultQuestionTime * 1000);
}

tl; dr: Bagaimana cara menemukan waktu yang tersisa sebelum javascript setTimeout ()?


Inilah solusi yang saya gunakan sekarang. Saya pergi ke bagian perpustakaan yang bertanggung jawab atas tes, dan menguraikan kode (mengerikan, dan bertentangan dengan izin saya).

// setup a timeout to go to the next question based on user-supplied time
var t = questionTime * 1000
test.currentTimeout = mySetTimeout( showNextQuestion(questions[i+1]), t );

dan inilah kode saya:

// wrapper untuk setTimeout
function mySetTimeout (func, timeout) {
    waktu tunggu [n = setTimeout (func, timeout)] = {
        mulai: Tanggal baru (). getTime (),
        akhir: Tanggal baru (). getTime () + batas waktu
        t: batas waktu
    }
    kembali n;
}

Ini bekerja cukup tepat di browser apa pun yang bukan IE 6. Bahkan iPhone asli, di mana saya mengharapkan semuanya menjadi asynchronous.

Jawaban:


31

Jika Anda tidak dapat mengubah kode perpustakaan, Anda harus mendefinisikan ulang setTimeout agar sesuai dengan tujuan Anda. Inilah contoh yang dapat Anda lakukan:

(function () {
var nativeSetTimeout = window.setTimeout;

window.bindTimeout = function (listener, interval) {
    function setTimeout(code, delay) {
        var elapsed = 0,
            h;

        h = window.setInterval(function () {
                elapsed += interval;
                if (elapsed < delay) {
                    listener(delay - elapsed);
                } else {
                    window.clearInterval(h);
                }
            }, interval);
        return nativeSetTimeout(code, delay);
    }

    window.setTimeout = setTimeout;
    setTimeout._native = nativeSetTimeout;
};
}());
window.bindTimeout(function (t) {console.log(t + "ms remaining");}, 100);
window.setTimeout(function () {console.log("All done.");}, 1000);

Ini bukan kode produksi, tetapi harus menempatkan Anda di jalur yang benar. Perhatikan bahwa Anda hanya dapat mengikat satu pendengar per waktu tunggu. Saya belum melakukan pengujian ekstensif dengan ini, tetapi berfungsi di Firebug.

Solusi yang lebih kuat akan menggunakan teknik penggabungan setTimeout yang sama, tetapi menggunakan peta dari timeoutId yang dikembalikan ke listener untuk menangani beberapa listener per waktu tunggu. Anda juga dapat mempertimbangkan untuk membungkus clearTimeout sehingga Anda dapat melepaskan pendengar Anda jika batas waktunya dihapus.


Terima kasih, Anda menyelamatkan hari saya!
Xecutioner

14
Karena waktu eksekusi, ini mungkin melayang beberapa milidetik selama beberapa saat. Cara yang lebih aman untuk melakukan ini, adalah mencatat waktu saat Anda memulai batas waktu (Tanggal baru ()) dan kemudian menguranginya dari waktu saat ini (Tanggal baru lainnya ())
Jonathan

61

Sebagai catatan, ada cara untuk mendapatkan waktu yang tersisa di node.js:

var timeout = setTimeout(function() {}, 3600 * 1000);

setInterval(function() {
    console.log('Time left: '+getTimeLeft(timeout)+'s');
}, 2000);

function getTimeLeft(timeout) {
    return Math.ceil((timeout._idleStart + timeout._idleTimeout - Date.now()) / 1000);
}

Cetakan:

$ node test.js 
Time left: 3599s
Time left: 3597s
Time left: 3595s
Time left: 3593s

Ini sepertinya tidak berfungsi di firefox, tetapi karena node.js adalah javascript, saya pikir komentar ini mungkin berguna untuk orang yang mencari solusi node.


5
tidak yakin apakah itu versi baru dari node atau apa tetapi agar ini bekerja saya harus menghapus bagian "getTime ()". sepertinya _idleStart sudah menjadi integer sekarang
kasztelan

3
Saya baru saja mencobanya di simpul 0.10, getTime()dihapus, _idleStartsudah waktunya
Tan Nguyen

2
tidak berfungsi pada node 6.3, karena struktur batas waktu adalah kebocoran informasi tersebut.
Huan

53

EDIT: Saya benar-benar berpikir saya membuat yang lebih baik: https://stackoverflow.com/a/36389263/2378102

Saya menulis fungsi ini dan saya sering menggunakannya:

function timer(callback, delay) {
    var id, started, remaining = delay, running

    this.start = function() {
        running = true
        started = new Date()
        id = setTimeout(callback, remaining)
    }

    this.pause = function() {
        running = false
        clearTimeout(id)
        remaining -= new Date() - started
    }

    this.getTimeLeft = function() {
        if (running) {
            this.pause()
            this.start()
        }

        return remaining
    }

    this.getStateRunning = function() {
        return running
    }

    this.start()
}

Buat pengatur waktu:

a = new timer(function() {
    // What ever
}, 3000)

Jadi jika Anda ingin waktu tersisa lakukan saja:

a.getTimeLeft()

Terima kasih untuk itu. Tidak persis seperti yang saya cari, tetapi fungsi untuk menghitung waktu yang tersisa sangat berguna
alih

4

Tumpukan acara Javascript tidak beroperasi seperti yang Anda pikirkan.

Saat peristiwa waktu tunggu dibuat, peristiwa itu ditambahkan ke antrean peristiwa, tetapi peristiwa lain mungkin diprioritaskan saat peristiwa itu diaktifkan, menunda waktu eksekusi dan menunda waktu proses.

Contoh: Anda membuat batas waktu dengan penundaan 10 detik untuk mengingatkan sesuatu ke layar. Ini akan ditambahkan ke tumpukan acara dan akan dijalankan setelah semua peristiwa saat ini diaktifkan (menyebabkan beberapa penundaan). Kemudian, saat waktu tunggu diproses, browser masih terus merekam peristiwa lain, menambahkannya ke tumpukan, yang menyebabkan penundaan lebih lanjut dalam pemrosesan. Jika pengguna mengklik, atau melakukan banyak pengetikan ctrl +, acara mereka akan diprioritaskan di atas tumpukan saat ini. 10 detik Anda bisa berubah menjadi 15 detik, atau lebih lama.


Meskipun demikian, ada banyak cara untuk memalsukan berapa lama waktu telah berlalu. Salah satu caranya adalah dengan menjalankan setInterval tepat setelah Anda menambahkan setTimeout ke stack.

Contoh: Lakukan settimeout dengan penundaan 10 detik (simpan penundaan itu secara global). Kemudian lakukan setInterval yang berjalan setiap detik untuk mengurangi 1 dari penundaan dan mengeluarkan sisa penundaan. Karena bagaimana tumpukan peristiwa dapat memengaruhi waktu sebenarnya (dijelaskan di atas), ini masih tidak akurat, tetapi memberikan hitungan.


Singkatnya, tidak ada cara nyata untuk mendapatkan sisa waktu. Hanya ada cara untuk mencoba dan menyampaikan perkiraan kepada pengguna.


4

Khusus sisi server Node.js

Tidak satu pun di atas yang benar-benar berhasil untuk saya, dan setelah memeriksa objek batas waktu, sepertinya semuanya relatif terhadap saat proses dimulai. Yang berikut berhasil untuk saya:

myTimer = setTimeout(function a(){console.log('Timer executed')},15000);

function getTimeLeft(timeout){
  console.log(Math.ceil((timeout._idleStart + timeout._idleTimeout)/1000 - process.uptime()));
}

setInterval(getTimeLeft,1000,myTimer);

Keluaran:

14
...
3
2
1
Timer executed
-0
-1
...

node -v
v9.11.1

Keluaran diedit agar singkatnya, tetapi fungsi dasar ini memberikan perkiraan waktu hingga eksekusi atau sejak eksekusi. Seperti yang disebutkan orang lain, tidak ada dari ini yang akan tepat karena cara proses node, tetapi jika saya ingin menekan permintaan yang dijalankan kurang dari 1 menit yang lalu, dan saya menyimpan pengatur waktu, saya tidak mengerti mengapa ini tidak terjadi. bekerja sebagai pemeriksaan cepat. Mungkin menarik untuk menyulap objek dengan refreshtimer di 10.2+.


1

Anda dapat memodifikasi setTimeout untuk menyimpan waktu akhir setiap waktu tunggu di peta dan membuat fungsi yang dipanggil getTimeoutuntuk mendapatkan waktu yang tersisa untuk waktu tunggu dengan id tertentu.

Ini adalah Super 's solusi , tapi saya dimodifikasi untuk menggunakan memori yang lebih sedikit

let getTimeout = (() => { // IIFE
    let _setTimeout = setTimeout, // Reference to the original setTimeout
        map = {}; // Map of all timeouts with their end times

    setTimeout = (callback, delay) => { // Modify setTimeout
        let id = _setTimeout(callback, delay); // Run the original, and store the id
        map[id] = Date.now() + delay; // Store the end time
        return id; // Return the id
    };

    return (id) => { // The actual getTimeout function
        // If there was no timeout with that id, return NaN, otherwise, return the time left clamped to 0
        return map[id] ? Math.max(map[id] - Date.now(), 0) : NaN;
    }
})();

Pemakaian:

// go home in 4 seconds
let redirectTimeout = setTimeout(() => {
    window.location.href = "/index.html";
}, 4000);

// display the time left until the redirect
setInterval(() => {
    document.querySelector("#countdown").innerHTML = `Time left until redirect ${getTimeout(redirectTimeout)}`;
},1);

Berikut versi minified dari getTimeout IIFE ini :

let getTimeout=(()=>{let t=setTimeout,e={};return setTimeout=((a,o)=>{let u=t(a,o);return e[u]=Date.now()+o,u}),t=>e[t]?Math.max(e[t]-Date.now(),0):NaN})();

Saya harap ini berguna bagi Anda seperti bagi saya! :)


0

Tidak, tetapi Anda dapat memiliki setTimeout / setInterval Anda sendiri untuk animasi di fungsi Anda.

Katakan pertanyaan Anda terlihat seperti ini:

function myQuestion() {
  // animate the progress bar for 1 sec
  animate( "progressbar", 1000 );

  // do the question stuff
  // ...
}

Dan animasi Anda akan ditangani oleh 2 fungsi berikut:

function interpolate( start, end, pos ) {
  return start + ( pos * (end - start) );
}

function animate( dom, interval, delay ) {

      interval = interval || 1000;
      delay    = delay    || 10;

  var start    = Number(new Date());

  if ( typeof dom === "string" ) {
    dom = document.getElementById( dom );
  }

  function step() {

    var now     = Number(new Date()),
        elapsed = now - start,
        pos     = elapsed / interval,
        value   = ~~interpolate( 0, 500, pos ); // 0-500px (progress bar)

    dom.style.width = value + "px";

    if ( elapsed < interval )
      setTimeout( step, delay );
  }

  setTimeout( step, delay );
}

nah perbedaannya hanya dapat diukur dalam ms, karena animasi dimulai di bagian atas fungsi Anda. Saya melihat Anda menggunakan jQuery. Anda dapat menggunakan jquery.animate.
gblazex

Cara terbaik adalah mencoba dan melihatnya sendiri. :)
gblazex

0

Jika ada yang melihat kembali ini. Saya telah keluar dengan manajer batas waktu dan interval yang dapat memberi Anda waktu yang tersisa dalam batas waktu atau interval serta melakukan beberapa hal lainnya. Saya akan menambahkannya untuk membuatnya lebih bagus dan lebih akurat, tetapi tampaknya berfungsi dengan cukup baik (meskipun saya memiliki beberapa ide lagi untuk membuatnya lebih akurat):

https://github.com/vhmth/Tock


tautan Anda rusak
Kick Buttowski

0

Pertanyaan telah dijawab tetapi saya akan menambahkan sedikit. Itu baru saja terjadi pada saya.

Gunakan setTimeoutdi recursionsebagai berikut:

var count = -1;

function beginTimer()
{
    console.log("Counting 20 seconds");
    count++;

    if(count <20)
    {
        console.log(20-count+"seconds left");
        setTimeout(beginTimer,2000);
    }
    else
    {
        endTimer();
    }
}

function endTimer()
{
    console.log("Time is finished");
}

Saya kira kodenya cukup jelas


0

Periksa ini:

class Timer {
  constructor(fun,delay) {
    this.timer=setTimeout(fun, delay)
    this.stamp=new Date()
  }
  get(){return ((this.timer._idleTimeout - (new Date-this.stamp))/1000) }
  clear(){return (this.stamp=null, clearTimeout(this.timer))}
}

Buat pengatur waktu:

let smtg = new Timer(()=>{do()}, 3000})

Dapatkan tetap:

smth.get()

Hapus waktu tunggu

smth.clear()

0
    (function(){
        window.activeCountdowns = [];
        window.setCountdown = function (code, delay, callback, interval) {
            var timeout = delay;
            var timeoutId = setTimeout(function(){
                clearCountdown(timeoutId);
                return code();
            }, delay);
            window.activeCountdowns.push(timeoutId);
            setTimeout(function countdown(){
                var key = window.activeCountdowns.indexOf(timeoutId);
                if (key < 0) return;
                timeout -= interval;
                setTimeout(countdown, interval);
                return callback(timeout);
            }, interval);
            return timeoutId;
        };
        window.clearCountdown = function (timeoutId) {
            clearTimeout(timeoutId);
            var key = window.activeCountdowns.indexOf(timeoutId);
            if (key < 0) return;
            window.activeCountdowns.splice(key, 1);
        };
    })();

    //example
    var t = setCountdown(function () {
        console.log('done');
    }, 15000, function (i) {
        console.log(i / 1000);
    }, 1000);

0

Saya mampir di sini mencari jawaban ini, tetapi terlalu memikirkan masalah saya. Jika Anda berada di sini karena Anda hanya perlu melacak waktu saat Anda setTimeout sedang berlangsung, berikut cara lain untuk melakukannya:

    var focusTime = parseInt(msg.time) * 1000

    setTimeout(function() {
        alert('Nice Job Heres 5 Schrute bucks')
        clearInterval(timerInterval)
    }, focusTime)

    var timerInterval = setInterval(function(){
        focusTime -= 1000
        initTimer(focusTime / 1000)
    }, 1000);

0

Cara yang lebih cepat dan mudah:

tmo = 1000;
start = performance.now();
setTimeout(function(){
    foo();
},tmo);

Anda bisa mendapatkan waktu tersisa dengan:

 timeLeft = tmo - (performance.now() - start);
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.