trova il tempo rimasto in un setTimeout ()?


90

Sto scrivendo del Javascript che interagisce con il codice della libreria che non possiedo e che non posso (ragionevolmente) modificare. Crea timeout Javascript utilizzati per mostrare la domanda successiva in una serie di domande a tempo limitato. Questo non è un codice reale perché è offuscato oltre ogni speranza. Ecco cosa sta facendo la libreria:

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

Voglio mettere una barra di avanzamento sullo schermo che si riempie questionTime * 1000interrogando il timer creato da setTimeout. L'unico problema è che non sembra esserci alcun modo per farlo. C'è una getTimeoutfunzione che mi manca? Le uniche informazioni sui timeout Javascript che posso trovare sono relative solo alla creazione tramite setTimeout( function, time)e alla cancellazione tramite clearTimeout( id ).

Sto cercando una funzione che restituisca il tempo rimanente prima che si attivi un timeout o il tempo trascorso dopo che è stato chiamato un timeout. Il mio codice a barre di avanzamento ha questo aspetto:

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: come faccio a trovare il tempo rimanente prima di un javascript setTimeout ()?


Ecco la soluzione che sto usando ora. Ho esaminato la sezione della libreria che si occupa dei test e ho decodificato il codice (terribile e contro i miei permessi).

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

ed ecco il mio codice:

// wrapper per setTimeout
function mySetTimeout (func, timeout) {
    timeout [n = setTimeout (func, timeout)] = {
        inizio: nuova data (). getTime (),
        fine: nuova data (). getTime () + timeout
        t: timeout
    }
    return n;
}

Funziona perfettamente in qualsiasi browser che non sia IE 6. Anche l'iPhone originale, dove mi aspettavo che le cose diventassero asincrone.

Risposte:


31

Se non puoi modificare il codice della libreria, dovrai ridefinire setTimeout in base ai tuoi scopi. Ecco un esempio di cosa potresti fare:

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

Questo non è un codice di produzione, ma dovrebbe metterti sulla strada giusta. Nota che puoi associare solo un listener per timeout. Non ho eseguito test approfonditi con questo, ma funziona in Firebug.

Una soluzione più robusta userebbe la stessa tecnica di wrapping di setTimeout, ma userebbe invece una mappa dal timeoutId restituito ai listener per gestire più listener per timeout. Potresti anche considerare di avvolgere clearTimeout in modo da poter scollegare il tuo listener se il timeout è cancellato.


Grazie Mi hai salvato la giornata!
Procuratore

14
A causa del tempo di esecuzione, questo potrebbe variare di alcuni millisecondi nell'arco di un po '. Un modo più sicuro per farlo, è registrare l'ora in cui hai iniziato il timeout (nuova data ()) e poi sottrarla dall'ora corrente (un'altra nuova data ())
Jonathan

61

Solo per la cronaca, c'è un modo per ottenere il tempo rimanente in 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);
}

Stampe:

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

Questo non sembra funzionare in Firefox, ma poiché node.js è javascript, ho pensato che questa osservazione potrebbe essere utile per le persone che cercano la soluzione del nodo.


5
non sono sicuro se si tratta di una nuova versione di node o cosa, ma per farlo funzionare ho dovuto rimuovere la parte "getTime ()". sembra che _idleStart sia già un numero intero ora
kasztelan

3
L'ho appena provato nel nodo 0.10, getTime()è stato rimosso, _idleStartè già l'ora
Tan Nguyen

2
non funziona sul nodo 6.3, perché la struttura del timeout è una perdita di quelle informazioni.
Huan

53

EDIT: In realtà penso di averne fatto uno ancora migliore: https://stackoverflow.com/a/36389263/2378102

Ho scritto questa funzione e la uso molto:

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

Crea un timer:

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

Quindi, se vuoi il tempo rimanente, fai:

a.getTimeLeft()

Grazie per quello. Non esattamente quello che stavo cercando, ma la funzione per contare il tempo rimasto è molto utile
invece il

4

Gli stack di eventi di Javascript non funzionano come penseresti.

Quando viene creato un evento di timeout, viene aggiunto alla coda degli eventi, ma altri eventi possono avere la priorità durante l'attivazione dell'evento, ritardare il tempo di esecuzione e posticipare il runtime.

Esempio: crei un timeout con un ritardo di 10 secondi per avvisare qualcosa sullo schermo. Verrà aggiunto allo stack degli eventi e verrà eseguito dopo che tutti gli eventi correnti sono stati attivati ​​(causando un certo ritardo). Quindi, quando il timeout viene elaborato, il browser continua a catturare altri eventi aggiungendoli allo stack, il che causa ulteriori ritardi nell'elaborazione. Se l'utente fa clic o fa molto ctrl + digitazione, i suoi eventi hanno la priorità sullo stack corrente. I tuoi 10 secondi possono trasformarsi in 15 secondi o più.


Detto questo, ci sono molti modi per fingere quanto tempo è passato. Un modo è eseguire un setInterval subito dopo aver aggiunto setTimeout allo stack.

Esempio: eseguire un settimeout con un ritardo di 10 secondi (memorizzare tale ritardo in un globale). Quindi eseguire un setInterval che viene eseguito ogni secondo per sottrarre 1 dal ritardo e visualizzare il ritardo rimanente. A causa di come lo stack degli eventi può influenzare il tempo effettivo (descritto sopra), questo non sarà ancora accurato, ma fornisce un conteggio.


In breve, non esiste un vero modo per ottenere il tempo rimanente. Ci sono solo modi per provare a trasmettere un preventivo all'utente.


4

Specifico per Node.js lato server

Nessuno dei precedenti ha funzionato davvero per me e dopo aver ispezionato l'oggetto timeout sembrava che tutto fosse relativo a quando è iniziato il processo. Quanto segue ha funzionato per me:

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

Produzione:

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

node -v
v9.11.1

Output modificato per brevità, ma questa funzione di base fornisce un tempo approssimativo fino all'esecuzione o dall'esecuzione. Come altri menzionano, niente di tutto questo sarà esatto a causa del modo in cui il nodo elabora, ma se voglio sopprimere una richiesta che è stata eseguita meno di 1 minuto fa e ho memorizzato il timer, non vedo perché questo non dovrebbe funziona come un controllo rapido. Potrebbe essere interessante destreggiarsi tra gli oggetti con il timer di aggiornamento nella versione 10.2+.


1

È possibile modificare setTimeout per memorizzare l'ora di fine di ogni timeout in una mappa e creare una funzione chiamata getTimeoutper ottenere il tempo rimanente per un timeout con un certo id.

Questa era una soluzione eccellente , ma l'ho modificata per utilizzare un po 'meno memoria

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

Utilizzo:

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

Ecco una versione ridotta di questo 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})();

Spero che questo ti sia utile come lo è stato per me! :)


0

No, ma puoi avere il tuo setTimeout / setInterval per l'animazione nella tua funzione.

Supponi che la tua domanda sia simile a questa:

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

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

E la tua animazione sarà gestita da queste 2 funzioni:

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

beh, la differenza può essere misurata solo in ms, perché l'animazione inizia all'inizio della tua funzione. Ti ho visto usare jQuery. Allora puoi usare jquery.animate.
gblazex

Il modo migliore è provare a vederlo di persona. :)
gblazex

0

Se qualcuno sta guardando indietro su questo. Sono uscito con un timeout e un gestore di intervalli che possono farti avere il tempo rimanente in un timeout o in un intervallo, oltre a fare altre cose. Lo aggiungerò per renderlo più elegante e accurato, ma sembra funzionare abbastanza bene così com'è (anche se ho qualche idea in più per renderlo ancora più preciso):

https://github.com/vhmth/Tock


i tuoi collegamenti sono interrotti
Kick Buttowski

0

La domanda è già stata risolta ma aggiungerò la mia parte. Mi è appena successo.

Utilizzare setTimeoutin recursionmodo seguente:

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

Immagino che il codice sia autoesplicativo


0

Controlla questo:

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

Crea un timer:

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

Rimani:

smth.get()

Cancella timeout

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

Mi sono fermato qui cercando questa risposta, ma stavo pensando troppo al mio problema. Se sei qui perché devi solo tenere traccia del tempo mentre sei impostato, il timeout è in corso, ecco un altro modo per farlo:

    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

Un modo più rapido e semplice:

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

Puoi ottenere il tempo rimanente con:

 timeLeft = tmo - (performance.now() - start);
Utilizzando il nostro sito, riconosci di aver letto e compreso le nostre Informativa sui cookie e Informativa sulla privacy.
Licensed under cc by-sa 3.0 with attribution required.