setTimeout () 'da kalan zamanı bulmak?


91

Sahip olmadığım ve (makul bir şekilde) değiştiremediğim kütüphane koduyla etkileşime giren bir Javascript yazıyorum. Bir sonraki soruyu bir dizi zaman sınırlı soruda göstermek için kullanılan Javascript zaman aşımlarını oluşturur. Bu gerçek bir kod değildir, çünkü tüm umutların ötesinde gizlenmiştir. İşte kütüphanenin yaptığı şey:

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

questionTime * 1000Oluşturduğu zamanlayıcıyı sorgulayarak ekrana doğru dolduran bir ilerleme çubuğu koymak istiyorum setTimeout. Tek sorun, bunu yapmanın bir yolu yok gibi görünüyor. Kaçırdığım bir getTimeoutişlev var mı ? Javascript zaman aşımlarıyla ilgili bulabildiğim tek bilgi, yalnızca aracılığıyla oluşturma setTimeout( function, time)ve aracılığıyla silme ile ilgili clearTimeout( id ).

Bir zaman aşımı tetiklenmeden önce kalan süreyi veya bir zaman aşımı çağrıldıktan sonra geçen süreyi döndüren bir işlev arıyorum. İlerleme barkodum şöyle görünüyor:

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: Bir javascript setTimeout () öncesinde kalan süreyi nasıl bulabilirim?


İşte şimdi kullandığım çözüm. Testlerden sorumlu kütüphane bölümünden geçtim ve kodu çözdüm (korkunç ve izinlerime aykırı).

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

ve işte kodum:

// setTimeout için sarmalayıcı
function mySetTimeout (func, timeout) {
    zaman aşımı [n = setTimeout (func, timeout)] = {
        başlangıç: yeni Date (). getTime (),
        end: new Date (). getTime () + zaman aşımı
        t: zaman aşımı
    }
    dönüş n;
}

Bu, IE 6 olmayan herhangi bir tarayıcıda oldukça yerinde çalışıyor. İşlerin zaman uyumsuz olmasını beklediğim orijinal iPhone bile.

Yanıtlar:


31

Kitaplık kodunu değiştiremezseniz, setTimeout'u amaçlarınıza uyacak şekilde yeniden tanımlamanız gerekir. İşte yapabileceklerinizin bir örneği:

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

Bu üretim kodu değildir, ancak sizi doğru yola sokmalıdır. Zaman aşımı başına yalnızca bir dinleyici bağlayabileceğinizi unutmayın. Bununla kapsamlı testler yapmadım, ancak Firebug'da çalışıyor.

Daha sağlam bir çözüm, setTimeout ile aynı sarma tekniğini kullanır, ancak bunun yerine, zaman aşımı başına birden çok dinleyiciyi işlemek için döndürülen timeoutId'den dinleyicilere bir harita kullanır. Ayrıca, zaman aşımı ortadan kalktığında dinleyicinizi ayırabilmek için clearTimeout'u kaydırmayı da düşünebilirsiniz.


Teşekkürler Günümü kurtardın!
xecutioner

15
Yürütme süresinden dolayı, bu bir süre içinde birkaç milisaniye kayabilir. Bunu yapmanın daha güvenli bir yolu, zaman aşımına başladığınız zamanı kaydetmek (yeni Tarih ()) ve sonra onu geçerli zamandan çıkarmaktır (başka bir yeni Tarih ())
Jonathan

62

Kayıt için, node.js'de kalan süreyi almanın bir yolu var:

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

Baskılar:

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

Bu, firefox'ta çalışmıyor gibi görünüyor, ancak node.js javascript olduğundan, bu açıklamanın düğüm çözümü arayan insanlar için yararlı olabileceğini düşündüm.


5
node'un yeni sürümü olup olmadığından emin değilim ama bunun çalışması için "getTime ()" bölümünü kaldırmam gerekiyordu. Görünüşe göre _idleStart zaten bir tamsayı
kasztelan

3
0.10 düğümünde denedim getTime(), kaldırıldı, _idleStartzaten zamanı geldi
Tan Nguyen

2
6.3 düğümü üzerinde çalışmaz, çünkü zaman aşımı yapısı bu bilgilerin sızdırılmasıdır.
Huan

54

DÜZENLEME: Aslında daha da iyisini yaptığımı düşünüyorum: https://stackoverflow.com/a/36389263/2378102

Bu işlevi yazdım ve çok kullanıyorum:

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

Bir zamanlayıcı yapın:

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

Öyleyse kalan süreyi istiyorsanız şunu yapın:

a.getTimeLeft()

Bunun için teşekkürler. Tam olarak aradığım şey değil, ancak kalan süreyi sayma işlevi çok kullanışlı
bunun yerine

4

Javascript'in olay yığınları düşündüğünüz gibi işlemiyor.

Bir zaman aşımı olayı oluşturulduğunda, olay kuyruğuna eklenir, ancak bu olay tetiklenirken diğer olaylar öncelik alabilir, yürütme süresini ve çalışma süresini erteleyebilir.

Örnek: Ekrandaki bir şeyi uyarmak için 10 saniyelik bir gecikmeyle bir zaman aşımı yaratıyorsunuz. Olay yığınına eklenecek ve tüm mevcut olaylar tetiklendikten sonra yürütülecektir (biraz gecikmeye neden olur). Daha sonra, zaman aşımı işlendiğinde, tarayıcı diğer olayları yakalamaya devam eder ve bunları yığına ekler, bu da işlemde daha fazla gecikmeye neden olur. Kullanıcı tıklarsa veya çok fazla ctrl + yazarsa, etkinlikleri mevcut yığına göre önceliklidir. 10 saniyeniz 15 saniyeye veya daha uzun sürebilir.


Bununla birlikte, ne kadar zaman geçtiğini taklit etmenin birçok yolu var. Bir yol, setTimeout'u yığına ekledikten hemen sonra bir setInterval yürütmektir.

Örnek: 10 saniyelik bir gecikmeyle ayar zaman aşımı gerçekleştirin (bu gecikmeyi bir globalde saklayın). Ardından, gecikmeden 1 çıkarmak ve kalan gecikmeyi çıkarmak için her saniye çalışan bir setInterval gerçekleştirin. Olay yığınının gerçek zamanı nasıl etkileyebildiğinden dolayı (yukarıda açıklanmıştır), bu yine de doğru olmayacaktır, ancak bir sayıdır.


Kısacası kalan zamanı almanın gerçek bir yolu yok. Kullanıcıya bir tahmin yapmayı denemenin ve iletmenin yalnızca yolları vardır.


4

Sunucu tarafı Node.js'ye özgü

Yukarıdakilerin hiçbiri benim için gerçekten işe yaramadı ve zaman aşımı nesnesini inceledikten sonra her şey işlemin başladığı zamana göreymiş gibi görünüyordu. Aşağıdakiler benim için çalıştı:

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

Çıktı:

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

node -v
v9.11.1

Kısalık için düzenlenmiş çıktı, ancak bu temel işlev, yürütmeye veya çalıştırmadan bu yana yaklaşık bir süre verir. Başkalarının da belirttiği gibi, bunların hiçbiri düğümün işleme biçiminden dolayı kesin olmayacak, ancak 1 dakikadan daha kısa bir süre önce çalıştırılan bir isteği bastırmak istersem ve zamanlayıcıyı sakladıysam, bunun neden olmayacağını anlamıyorum hızlı bir kontrol olarak çalışın. 10.2+ sürümünde refreshtimer ile nesneleri hokkabazlık etmek ilginç olabilir.


1

Her zaman aşımının bitiş zamanını bir haritada saklamak için değişiklik yapabilir setTimeoutve getTimeoutbelirli bir kimlik ile bir zaman aşımı için kalan süreyi almak için çağrılan bir işlev oluşturabilirsiniz .

Bu oldu süper 'ın çözüm , ama biraz daha az bellek kullanacak şekilde modifiye

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

Kullanım:

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

İşte bu getTimeout IIFE'nin küçültülmüş bir versiyonu :

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

Umarım bu benim için olduğu kadar sizin için de yararlıdır! :)


0

Hayır, ancak işlevinizdeki animasyon için kendi setTimeout / setInterval'iniz olabilir.

Sorunuzun şuna benzediğini söyleyin:

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

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

Ve animasyonunuz şu 2 işlev tarafından ele alınacaktır:

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

Pekala, fark yalnızca ms cinsinden ölçülebilir, çünkü animasyon işlevinizin en üstünde başlar. JQuery kullandığını gördüm. O zaman jquery.animate'ı kullanabilirsiniz.
gblazex

En iyi yol denemek ve kendin görmektir. :)
gblazex

0

Birisi geriye dönüp bakıyorsa. Size bir zaman aşımı veya aralıkta kalan süreyi almanın yanı sıra başka şeyler de yapabilen bir zaman aşımı ve aralık yöneticisi ile geldim. Daha şık ve daha doğru hale getirmek için ekleyeceğim, ancak olduğu gibi oldukça iyi çalışıyor gibi görünüyor (daha doğru hale getirmek için daha fazla fikrim olmasına rağmen):

https://github.com/vhmth/Tock


0

Soru zaten cevaplandı ama ben de biraz ekleyeceğim. Aklıma geldi.

Kullanım setTimeoutiçinde recursionşöyle:

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

Sanırım kod kendinden açıklamalı


0

Bunu kontrol et:

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

Bir zamanlayıcı yapın:

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

Kalın:

smth.get()

Zaman aşımını temizle

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

Buraya bu cevabı aramak için uğradım, ama sorunumu aşırı düşünüyordum. Eğer buradaysanız, sadece setTimeout devam ederken zamanı takip etmeniz gerekiyorsa, işte bunu yapmanın başka bir yolu:

    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

Daha hızlı ve daha kolay bir yol:

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

Kalan süreyi şununla alabilirsiniz:

 timeLeft = tmo - (performance.now() - start);
Sitemizi kullandığınızda şunları okuyup anladığınızı kabul etmiş olursunuz: Çerez Politikası ve Gizlilik Politikası.
Licensed under cc by-sa 3.0 with attribution required.