znaleźć czas pozostały w setTimeout ()?

90

Piszę trochę Javascript, który współdziała z kodem biblioteki, którego nie jestem właścicielem i nie mogę (rozsądnie) zmienić. Tworzy limity czasu JavaScript używane do wyświetlania następnego pytania w serii pytań ograniczonych w czasie. To nie jest prawdziwy kod, ponieważ jest zaciemniony poza wszelką nadzieją. Oto, co robi biblioteka:

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

Chcę umieścić na ekranie pasek postępu, który zapełnia się, questionTime * 1000sprawdzając licznik czasu utworzony przez setTimeout. Jedynym problemem jest to, że wydaje się, że nie da się tego zrobić. Czy jest getTimeoutfunkcja, której mi brakuje? Jedyne informacje na temat limitów czasu Javascript, które mogę znaleźć, dotyczą tylko tworzenia setTimeout( function, time)i usuwania przez clearTimeout( id ).

Szukam funkcji, która zwraca czas pozostały do ​​uruchomienia limitu czasu lub czas, który upłynął po wywołaniu limitu czasu. Mój kod paskowy postępu wygląda następująco:

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: Jak znaleźć czas pozostały do ​​wykonania setTimeout () w javascript?


Oto rozwiązanie, którego teraz używam. Przejrzałem sekcję biblioteki, która jest odpowiedzialna za testy, i rozszyfrowałem kod (okropne i wbrew moim uprawnieniom).

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

a oto mój kod:

// opakowanie dla setTimeout
function mySetTimeout (func, timeout) {
    timeouts [n = setTimeout (func, timeout)] = {
        start: new Date (). getTime (),
        end: new Date (). getTime () + timeout
        t: limit czasu
    }
    return n;
}

Działa to bardzo dobrze w każdej przeglądarce innej niż IE 6. Nawet w oryginalnym iPhonie, w którym spodziewałem się, że wszystko będzie asynchroniczne.

Tylko Jake
źródło

Odpowiedzi:

31

Jeśli nie możesz zmodyfikować kodu biblioteki, musisz przedefiniować setTimeout, aby odpowiadał Twoim celom. Oto przykład tego, co możesz zrobić:

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

To nie jest kod produkcyjny, ale powinien skierować Cię na właściwą ścieżkę. Zauważ, że możesz powiązać tylko jednego nasłuchiwania na limit czasu. Nie przeprowadziłem z tym obszernych testów, ale działa to w Firebug.

Bardziej niezawodne rozwiązanie wykorzystałoby tę samą technikę zawijania setTimeout, ale zamiast tego używałoby mapy z zwróconego timeoutId do detektorów, aby obsłużyć wiele detektorów na limit czasu. Możesz również rozważyć zawinięcie clearTimeout, abyś mógł odłączyć detektor, jeśli limit czasu zostanie wyczyszczony.

trawnik
źródło
Dziękuję Uratowałeś mój dzień!
xecutioner
14
Ze względu na czas wykonywania może to dryfować o kilka milisekund przez chwilę. Bardziej bezpiecznym sposobem na zrobienie tego jest zarejestrowanie czasu rozpoczęcia limitu czasu (new Date ()), a następnie po prostu odjęcie go od bieżącego czasu (kolejna nowa data ())
Jonathan
61

Tak dla przypomnienia, istnieje sposób na pobranie czasu pozostałego w 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);
}

Wydruki:

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

Wydaje się, że to nie działa w przeglądarce Firefox, ale ponieważ node.js jest javascript, pomyślałem, że ta uwaga może być pomocna dla osób szukających rozwiązania dla węzłów.

Puszysty
źródło
5
Nie jestem pewien, czy to nowa wersja węzła, czy co, ale aby to zadziałało, musiałem usunąć część "getTime ()". wygląda na to, że _idleStart jest już liczbą całkowitą
kasztelan
3
Właśnie wypróbowałem to w węźle 0.10, getTime()jest usunięty, _idleStartjest już czas
Tan Nguyen
2
nie działa na węźle 6.3, ponieważ struktura limitu czasu jest wyciekiem tych informacji.
Huan
53

EDYCJA: Właściwie myślę, że zrobiłem jeszcze lepszy: https://stackoverflow.com/a/36389263/2378102

Napisałem tę funkcję i często jej używam:

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

Zrób minutnik:

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

Więc jeśli chcesz, aby pozostał czas, po prostu zrób:

a.getTimeLeft()
Społeczność
źródło
Dziękuję za to. Nie jest to dokładnie to, czego szukałem, ale funkcja liczenia pozostałego czasu jest bardzo przydatna
zamiast tego
4

Stosy zdarzeń JavaScript nie działają tak, jak myślisz.

Po utworzeniu zdarzenia przekroczenia limitu czasu jest ono dodawane do kolejki zdarzeń, ale inne zdarzenia mogą mieć priorytet podczas wyzwalania tego zdarzenia, opóźniać czas wykonania i opóźniać czas wykonywania.

Przykład: Tworzysz limit czasu z opóźnieniem 10 sekund, aby zaalarmować coś na ekranie. Zostanie dodany do stosu zdarzeń i zostanie wykonany po uruchomieniu wszystkich bieżących zdarzeń (powodując pewne opóźnienie). Następnie po przetworzeniu limitu czasu przeglądarka nadal przechwytuje inne zdarzenia, dodaje je do stosu, co powoduje dalsze opóźnienia w przetwarzaniu. Jeśli użytkownik kliknie lub często wciska klawisze Ctrl + klawisz, jego zdarzenia mają pierwszeństwo przed bieżącym stosem. Twoje 10 sekund może zmienić się w 15 sekund lub dłużej.


Biorąc to pod uwagę, istnieje wiele sposobów, aby udawać, ile czasu minęło. Jednym ze sposobów jest wykonanie setInterval zaraz po dodaniu setTimeout do stosu.

Przykład: Ustaw limit czasu z 10-sekundowym opóźnieniem (zapisz to opóźnienie w globalnej). Następnie wykonaj setInterval, który jest uruchamiany co sekundę, aby odjąć 1 od opóźnienia i wyprowadzić pozostałe opóźnienie. Ze względu na to, jak stos zdarzeń może wpływać na rzeczywisty czas (opisany powyżej), nadal nie będzie to dokładne, ale daje liczbę.


Krótko mówiąc, nie ma prawdziwego sposobu na uzyskanie pozostałego czasu. Są tylko sposoby, aby spróbować przekazać użytkownikowi oszacowanie.

vol7ron
źródło
4

Specyficzne dla Node.js po stronie serwera

Żadne z powyższych nie działało dla mnie, a po sprawdzeniu obiektu limitu czasu wyglądało na to, że wszystko było zależne od momentu rozpoczęcia procesu. Pracowały dla mnie:

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

Wynik:

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

node -v
v9.11.1

Edytowano dane wyjściowe dla zwięzłości, ale ta podstawowa funkcja podaje przybliżony czas do wykonania lub od wykonania. Jak wspominają inni, nic z tego nie będzie dokładne ze względu na sposób, w jaki węzeł przetwarza, ale jeśli chcę zablokować żądanie, które zostało uruchomione mniej niż 1 minutę temu, i zapisałem licznik czasu, nie rozumiem, dlaczego nie działa jako szybki test. Żonglowanie obiektami za pomocą odświeżacza w wersji 10.2+ może być interesujące.

John Watts
źródło
1

Możesz zmodyfikować, setTimeout aby zapisać czas zakończenia każdego limitu czasu na mapie i utworzyć funkcję wywoływaną w getTimeoutcelu uzyskania czasu pozostałego do limitu czasu z określonym identyfikatorem.

To był wspaniały jest rozwiązanie , ale zmodyfikowano go użyć nieco mniej pamięci

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

Stosowanie:

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

Oto getTimeout zminimalizowana wersja tego IIFE :

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

Mam nadzieję, że jest to dla Ciebie tak samo przydatne, jak dla mnie! :)

Kimeiga
źródło
0

Nie, ale możesz mieć swój własny setTimeout / setInterval dla animacji w swojej funkcji.

Powiedz, że Twoje pytanie wygląda następująco:

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

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

Twoja animacja będzie obsługiwana przez te 2 funkcje:

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 );
}
gblazex
źródło
cóż, różnicę można mierzyć tylko w ms, ponieważ animacja zaczyna się na górze funkcji. Widziałem, że używasz jQuery. Możesz wtedy użyć jquery.animate.
gblazex
Najlepszym sposobem jest przekonanie się o tym na własne oczy. :)
gblazex
0

Jeśli ktoś patrzy wstecz. Wyszedłem z menedżera limitu czasu i interwałów, który może uzyskać czas pozostały w limicie czasu lub interwale, a także zrobić kilka innych rzeczy. Dodam do tego, aby był ładniejszy i dokładniejszy, ale wydaje się, że działa całkiem nieźle, jak jest (chociaż mam więcej pomysłów, aby był jeszcze dokładniejszy):

https://github.com/vhmth/Tock

Vinay
źródło
twoje linki są zepsute
Kick Buttowski
0

Odpowiedź na pytanie już została udzielona, ​​ale dodam swoją część. Po prostu przyszło mi to do głowy.

Użyj setTimeoutw recursionnastępujący sposób:

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

Myślę, że kod jest oczywisty

dasfdsa
źródło
0

Sprawdź ten:

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

Zrób minutnik:

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

Pozostań:

smth.get()

Wyczyść limit czasu

smth.clear()
crokudripi
źródło
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);
Vitalik s2pac
źródło
0

Zatrzymałem się tutaj, szukając odpowiedzi, ale zastanawiałem się nad swoim problemem. Jeśli jesteś tutaj, ponieważ musisz po prostu śledzić czas podczas ustawiania limitu czasu, oto inny sposób, aby to zrobić:

    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);
Jbur43
źródło
0

Szybszy i łatwiejszy sposób:

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

Pozostały czas możesz uzyskać dzięki:

 timeLeft = tmo - (performance.now() - start);
Danial
źródło