trouver le temps restant dans un setTimeout ()?

90

J'écris du Javascript qui interagit avec le code de bibliothèque que je ne possède pas et que je ne peux pas (raisonnablement) changer. Il crée des délais d'expiration Javascript utilisés pour afficher la question suivante dans une série de questions limitées dans le temps. Ce n'est pas du vrai code car il est obscurci au-delà de tout espoir. Voici ce que fait la bibliothèque:

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

Je veux mettre une barre de progression à l'écran qui se remplit questionTime * 1000en interrogeant la minuterie créée par setTimeout. Le seul problème est qu'il ne semble y avoir aucun moyen de le faire. Y a-t-il une getTimeoutfonction qui me manque? Les seules informations sur les délais d'expiration Javascript que je peux trouver concernent uniquement la création via setTimeout( function, time)et la suppression via clearTimeout( id ).

Je recherche une fonction qui renvoie soit le temps restant avant le déclenchement d'un timeout, soit le temps écoulé après l'appel d'un timeout. Mon code à barres de progression ressemble à ceci:

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: Comment trouver le temps restant avant un javascript setTimeout ()?


Voici la solution que j'utilise actuellement. J'ai parcouru la section bibliothèque chargée des tests, et j'ai déchiffré le code (terrible et contre mes autorisations).

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

et voici mon code:

// wrapper pour setTimeout
function mySetTimeout (func, timeout) {
    timeouts [n = setTimeout (func, timeout)] = {
        début: nouvelle Date (). getTime (),
        end: new Date (). getTime () + timeout
        t: délai d'expiration
    }
    return n;
}

Cela fonctionne assez parfaitement dans n'importe quel navigateur qui n'est pas IE 6. Même l'iPhone d'origine, où je m'attendais à ce que les choses deviennent asynchrones.

Juste Jake
la source

Réponses:

31

Si vous ne pouvez pas modifier le code de la bibliothèque, vous devrez redéfinir setTimeout en fonction de vos besoins. Voici un exemple de ce que vous pourriez faire:

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

Ce n'est pas du code de production, mais cela devrait vous mettre sur la bonne voie. Notez que vous ne pouvez lier qu'un seul écouteur par timeout. Je n'ai pas fait de tests approfondis avec cela, mais cela fonctionne dans Firebug.

Une solution plus robuste utiliserait la même technique d'encapsulation de setTimeout, mais utiliserait à la place une mappe du timeoutId renvoyé aux écouteurs pour gérer plusieurs écouteurs par timeout. Vous pouvez également envisager d'encapsuler clearTimeout afin de pouvoir détacher votre écouteur si le délai d'attente est effacé.

pelouse
la source
Merci Vous avez sauvé ma journée!
xecutioner
14
En raison du temps d'exécution, cela peut dériver de quelques millisecondes pendant un certain temps. Un moyen plus sûr de le faire est d'enregistrer l'heure à laquelle vous avez commencé le délai d'expiration (nouvelle date ()), puis de la soustraire simplement de l'heure actuelle (une autre nouvelle date ())
Jonathan
61

Pour mémoire, il existe un moyen d'obtenir le temps restant dans 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);
}

Impressions:

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

Cela ne semble pas fonctionner dans Firefox, mais comme node.js est javascript, j'ai pensé que cette remarque pourrait être utile pour les personnes à la recherche de la solution de nœud.

Duveteux
la source
5
Je ne sais pas si c'est la nouvelle version du nœud ou quoi, mais pour que cela fonctionne, j'ai dû supprimer la partie "getTime ()". il semble que _idleStart est déjà un entier maintenant
kasztelan
3
Je viens de l'essayer dans le nœud 0.10, getTime()est supprimé, _idleStartc'est déjà l'heure
Tan Nguyen
2
ne fonctionne pas sur le nœud 6.3, car la structure du délai d'expiration est une fuite de ces informations.
Huan
53

EDIT: Je pense en fait que j'ai fait un encore meilleur: https://stackoverflow.com/a/36389263/2378102

J'ai écrit cette fonction et je l'utilise beaucoup:

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

Faire une minuterie:

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

Donc, si vous voulez le temps restant, faites simplement:

a.getTimeLeft()
Communauté
la source
Merci pour ça. Pas exactement ce que je cherchais, mais la fonction de compter le temps restant est très utile
au lieu de
4

Les piles d'événements de Javascript ne fonctionnent pas comme vous le pensez.

Lorsqu'un événement de délai d'expiration est créé, il est ajouté à la file d'attente d'événements, mais d'autres événements peuvent avoir la priorité pendant le déclenchement de cet événement, retardent le temps d'exécution et reportent le temps d'exécution.

Exemple: vous créez une temporisation avec un délai de 10 secondes pour alerter quelque chose à l'écran. Il sera ajouté à la pile d'événements et sera exécuté après le déclenchement de tous les événements actuels (entraînant un certain retard). Ensuite, lorsque le délai d'expiration est traité, le navigateur continue de capturer d'autres événements et les ajoute à la pile, ce qui entraîne des retards supplémentaires dans le traitement. Si l'utilisateur clique ou fait beaucoup de ctrl + tapant, ses événements ont priorité sur la pile actuelle. Vos 10 secondes peuvent devenir 15 secondes ou plus.


Cela étant dit, il existe de nombreuses façons de simuler le temps écoulé. Une façon consiste à exécuter un setInterval juste après avoir ajouté le setTimeout à la pile.

Exemple: effectuer un règlement avec un délai de 10 secondes (stocker ce délai dans un global). Ensuite, effectuez un setInterval qui s'exécute toutes les secondes pour soustraire 1 du délai et afficher le délai restant. En raison de la façon dont la pile d'événements peut influencer le temps réel (décrit ci-dessus), cela ne sera toujours pas précis, mais donne un décompte.


En bref, il n'y a pas vraiment de moyen d'obtenir le temps restant. Il n'y a que des moyens d'essayer de transmettre une estimation à l'utilisateur.

vol7ron
la source
4

Spécifique à Node.js côté serveur

Rien de ce qui précède n'a vraiment fonctionné pour moi, et après avoir inspecté l'objet timeout, il semblait que tout était relatif au démarrage du processus. Ce qui suit a fonctionné pour moi:

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

Production:

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

node -v
v9.11.1

Sortie éditée par souci de concision, mais cette fonction de base donne un temps approximatif jusqu'à l'exécution ou depuis l'exécution. Comme d'autres le mentionnent, rien de tout cela ne sera exact en raison de la façon dont le nœud traite, mais si je veux supprimer une requête qui a été exécutée il y a moins d'une minute et que j'ai stocké le minuteur, je ne vois pas pourquoi cela ne le ferait pas fonctionne comme un contrôle rapide. Cela pourrait être intéressant de jongler avec des objets avec refreshtimer dans 10.2+.

John Watts
la source
1

Vous pouvez modifier setTimeout pour stocker l'heure de fin de chaque timeout dans une carte et créer une fonction appelée getTimeoutpour obtenir le temps restant pour un timeout avec un certain identifiant.

C'était la solution super , mais je l'ai modifiée pour utiliser un peu moins de mémoire

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

Usage:

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

Voici une version getTimeout réduite de cet 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})();

J'espère que cela vous sera aussi utile qu'à moi! :)

Kimeiga
la source
0

Non, mais vous pouvez avoir votre propre setTimeout / setInterval pour l'animation dans votre fonction.

Dites que votre question ressemble à ceci:

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

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

Et votre animation sera gérée par ces 2 fonctions:

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
la source
eh bien la différence ne peut être mesurée qu'en ms, car l'animation commence en haut de votre fonction. Je vous ai vu utiliser jQuery. Vous pouvez alors utiliser jquery.animate.
gblazex
Le meilleur moyen est d'essayer de le voir par vous-même. :)
gblazex
0

Si quelqu'un regarde en arrière. Je suis sorti avec un gestionnaire de timeout et d'intervalle qui peut vous donner le temps restant dans un timeout ou un intervalle ainsi que faire d'autres choses. Je vais y ajouter pour le rendre plus astucieux et plus précis, mais il semble fonctionner assez bien tel quel (même si j'ai quelques idées supplémentaires pour le rendre encore plus précis):

https://github.com/vhmth/Tock

Vinay
la source
vos liens sont rompus
Kick Buttowski
0

La question a déjà reçu une réponse, mais j'ajouterai ma part. Cela m'est juste arrivé.

Utiliser setTimeoutdans la recursionmanière suivante:

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

Je suppose que le code est explicite

dasfdsa
la source
0

Vérifier celui-ci:

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

Faire une minuterie:

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

Restez:

smth.get()

Effacer le délai

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

Je me suis arrêté ici à la recherche de cette réponse, mais je réfléchissais trop à mon problème. Si vous êtes ici parce que vous avez juste besoin de garder une trace du temps pendant que vous êtes setTimeout est en cours, voici une autre façon de le faire:

    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
la source
0

Un moyen plus rapide et plus simple:

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

Vous pouvez obtenir le temps restant avec:

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