Finde die verbleibende Zeit in einem setTimeout ()?

89

Ich schreibe Javascript, das mit Bibliothekscode interagiert, den ich nicht besitze und den ich (vernünftigerweise) nicht ändern kann. Es werden Javascript-Zeitüberschreitungen erstellt, mit denen die nächste Frage in einer Reihe von zeitlich begrenzten Fragen angezeigt wird. Dies ist kein richtiger Code, da er hoffnungslos verschleiert ist. Folgendes macht die Bibliothek:

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

Ich möchte einen Fortschrittsbalken auf dem Bildschirm anzeigen, der sich questionTime * 1000durch Abfragen des von erstellten Timers füllt setTimeout. Das einzige Problem ist, es scheint keine Möglichkeit zu geben, dies zu tun. Gibt es eine getTimeoutFunktion, die mir fehlt? Die einzigen Informationen zu Javascript-Zeitüberschreitungen, die ich finden kann, beziehen sich nur auf das Erstellen über setTimeout( function, time)und das Löschen über clearTimeout( id ).

Ich suche nach einer Funktion, die entweder die verbleibende Zeit vor dem Auslösen eines Timeouts oder die nach dem Aufrufen eines Timeouts verstrichene Zeit zurückgibt. Mein Fortschritts-Barcode sieht folgendermaßen aus:

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: Wie finde ich die verbleibende Zeit vor einem Javascript setTimeout ()?


Hier ist die Lösung, die ich jetzt verwende. Ich ging durch den Bibliotheksbereich, der für Tests zuständig ist, und entschlüsselte den Code (schrecklich und gegen meine Berechtigungen).

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

und hier ist mein Code:

// Wrapper für setTimeout
Funktion mySetTimeout (func, timeout) {
    timeouts [n = setTimeout (func, timeout)] = {
        start: neues Datum (). getTime (),
        Ende: neues Datum (). getTime () + Zeitüberschreitung
        t: Zeitüberschreitung
    }}
    return n;
}}

Dies funktioniert in jedem Browser, der nicht IE 6 ist, ziemlich genau. Sogar auf dem ursprünglichen iPhone, auf dem ich erwartet hatte, dass die Dinge asynchron werden.

Nur Jake
quelle

Antworten:

28

Wenn Sie den Bibliothekscode nicht ändern können, müssen Sie setTimeout neu definieren, um es Ihren Zwecken anzupassen. Hier ist ein Beispiel dafür, was Sie tun könnten:

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

Dies ist kein Produktionscode, aber er sollte Sie auf den richtigen Weg bringen. Beachten Sie, dass Sie pro Timeout nur einen Listener binden können. Ich habe damit keine umfangreichen Tests durchgeführt, aber es funktioniert in Firebug.

Eine robustere Lösung würde dieselbe Technik zum Umschließen von setTimeout verwenden, aber stattdessen eine Zuordnung von der zurückgegebenen timeoutId zu Listenern verwenden, um mehrere Listener pro Timeout zu verarbeiten. Sie können auch clearTimeout einschließen, damit Sie Ihren Listener trennen können, wenn das Timeout gelöscht wird.

Rasensee
quelle
Danke Du hast meinen Tag gerettet!
Xecutioner
14
Aufgrund der Ausführungszeit kann dies über eine Weile um einige Millisekunden verschoben werden. Ein sicherer Weg, dies zu tun, besteht darin, die Zeit aufzuzeichnen, zu der Sie das Zeitlimit gestartet haben (neues Datum ()) und es dann einfach von der aktuellen Zeit (ein weiteres neues Datum ()) zu subtrahieren
Jonathan
61

Nur für die Aufzeichnung gibt es eine Möglichkeit, die verbleibende Zeit in node.js abzurufen:

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

Drucke:

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

Dies scheint in Firefox nicht durch zu funktionieren, aber da node.js Javascript ist, dachte ich, dass diese Bemerkung für Leute hilfreich sein könnte, die nach der Node-Lösung suchen.

Flauschige
quelle
5
Ich bin mir nicht sicher, ob es sich um eine neue Version von Node handelt oder was, aber damit dies funktioniert, musste ich den Teil "getTime ()" entfernen. es scheint, dass _idleStart jetzt schon eine ganze Zahl ist
kasztelan
3
Ich habe es gerade in Knoten 0.10 versucht, getTime()ist entfernt, _idleStartist schon die Zeit
Tan Nguyen
2
funktioniert nicht auf Knoten 6.3, da in der Timeout-Struktur diese Informationen verloren gehen.
Huan
53

EDIT: Ich denke tatsächlich, ich habe eine noch bessere gemacht: https://stackoverflow.com/a/36389263/2378102

Ich habe diese Funktion geschrieben und benutze sie oft:

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

Machen Sie einen Timer:

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

Wenn Sie also die verbleibende Zeit haben möchten, tun Sie einfach:

a.getTimeLeft()
Gemeinschaft
quelle
Dank dafür. Nicht genau das, wonach ich gesucht habe, aber die Funktion zum Zählen der verbleibenden Zeit ist sehr nützlich
stattdessen am
4

Die Event-Stacks von Javascript funktionieren nicht so, wie Sie denken würden.

Wenn ein Timeout-Ereignis erstellt wird, wird es der Ereigniswarteschlange hinzugefügt. Andere Ereignisse können jedoch Vorrang haben, während dieses Ereignis ausgelöst wird, die Ausführungszeit verzögern und die Laufzeit verschieben.

Beispiel: Sie erstellen eine Zeitüberschreitung mit einer Verzögerung von 10 Sekunden, um auf den Bildschirm aufmerksam zu machen. Es wird dem Ereignisstapel hinzugefügt und ausgeführt, nachdem alle aktuellen Ereignisse ausgelöst wurden (was zu Verzögerungen führt). Wenn das Zeitlimit verarbeitet wird, erfasst der Browser weiterhin andere Ereignisse und fügt sie dem Stapel hinzu, was zu weiteren Verzögerungen bei der Verarbeitung führt. Wenn der Benutzer klickt oder viel Strg + tippt, haben seine Ereignisse Vorrang vor dem aktuellen Stapel. Ihre 10 Sekunden können zu 15 Sekunden oder länger werden.


Davon abgesehen gibt es viele Möglichkeiten, um zu fälschen, wie viel Zeit vergangen ist. Eine Möglichkeit besteht darin, ein setInterval direkt nach dem Hinzufügen des setTimeout zum Stapel auszuführen.

Beispiel: Führen Sie ein Settimeout mit einer Verzögerung von 10 Sekunden durch (speichern Sie diese Verzögerung in einem globalen Format). Führen Sie dann ein setInterval aus, das jede Sekunde ausgeführt wird, um 1 von der Verzögerung zu subtrahieren und die verbleibende Verzögerung auszugeben. Aufgrund der Art und Weise, wie der Ereignisstapel die tatsächliche Zeit beeinflussen kann (siehe oben), ist dies immer noch nicht genau, gibt jedoch eine Zählung an.


Kurz gesagt, es gibt keinen wirklichen Weg, um die verbleibende Zeit zu bekommen. Es gibt nur Möglichkeiten, dem Benutzer eine Schätzung zu übermitteln.

vol7ron
quelle
4

Serverseitige Node.js spezifisch

Keines der oben genannten Verfahren hat bei mir wirklich funktioniert, und nach der Überprüfung des Timeout-Objekts sah es so aus, als ob alles relativ zu dem Zeitpunkt war, an dem der Prozess gestartet wurde. Folgendes hat bei mir funktioniert:

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

Ausgabe:

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

node -v
v9.11.1

Die Ausgabe wurde der Kürze halber bearbeitet, aber diese Grundfunktion gibt eine ungefähre Zeit bis zur Ausführung oder seit der Ausführung an. Wie andere erwähnen, ist nichts davon aufgrund der Art und Weise, wie der Knoten verarbeitet wird, genau. Wenn ich jedoch eine Anforderung unterdrücken möchte, die vor weniger als einer Minute ausgeführt wurde, und den Timer gespeichert habe, verstehe ich nicht, warum dies nicht der Fall ist Arbeit als schnelle Überprüfung. Könnte interessant sein, Objekte mit Refreshtimer in 10.2+ zu jonglieren.

John Watts
quelle
1

Sie können ändern setTimeout jedes Timeout der Endzeit in einer Karte zu speichern und eine Funktion erstellen , genannt getTimeoutdie Zeit , um nach links auf eine Zeitüberschreitung mit einer bestimmten ID.

Dies war die Lösung von Super , aber ich habe sie so geändert, dass sie etwas weniger Speicher benötigt

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

Verwendung:

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

Hier ist eine verkleinerte Version dieses getTimeout 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})();

Ich hoffe, das ist für Sie genauso nützlich wie für mich! :) :)

Kimeiga
quelle
0

Nein, aber Sie können Ihr eigenes setTimeout / setInterval für die Animation in Ihrer Funktion haben.

Angenommen, Ihre Frage sieht folgendermaßen aus:

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

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

Und Ihre Animation wird von diesen 2 Funktionen verwaltet:

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
quelle
Nun, der Unterschied kann nur in ms gemessen werden, da die Animation oben in Ihrer Funktion beginnt. Ich habe gesehen, dass Sie jQuery verwenden. Sie können dann jquery.animate verwenden.
Gblazex
Der beste Weg ist, es selbst zu versuchen. :)
Gblazex
0

Wenn jemand darauf zurückblickt. Ich habe einen Timeout- und Intervall-Manager herausgebracht, mit dem Sie die verbleibende Zeit in einem Timeout oder Intervall ermitteln und einige andere Aufgaben erledigen können. Ich werde es ergänzen, um es geschickter und genauer zu machen, aber es scheint ziemlich gut zu funktionieren (obwohl ich noch einige Ideen habe, um es noch genauer zu machen):

https://github.com/vhmth/Tock

Vinay
quelle
Ihre Links sind kaputt
Kick Buttowski
0

Die Frage wurde bereits beantwortet, aber ich werde mein Bit hinzufügen. Es ist mir gerade eingefallen.

Verwenden Sie setTimeoutin recursionwie folgt:

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

Ich denke, der Code ist selbsterklärend

dasfdsa
quelle
0

Überprüfen Sie dieses:

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

Machen Sie einen Timer:

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

Holen Sie sich bleiben:

smth.get()

Zeitüberschreitung löschen

smth.clear()
crokudripi
quelle
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
quelle
0

Ich habe hier nach dieser Antwort gesucht, aber mein Problem überlegt. Wenn Sie hier sind, weil Sie nur die Zeit verfolgen müssen, während Sie setTimeout ausführen, haben Sie folgende Möglichkeit:

    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
quelle
0

Ein schneller und einfacher Weg:

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

Sie können die verbleibende Zeit erhalten mit:

 timeLeft = tmo - (performance.now() - start);
Danial
quelle