97 votes

trouver le temps restant dans un setTimeout() ?

J'écris du Javascript qui interagit avec du code de bibliothèque qui ne m'appartient pas et que je ne peux pas (raisonnablement) modifier. Il crée des délais d'attente 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 vers questionTime * 1000 en interrogeant le timer créé par setTimeout . Le seul problème est qu'il semble n'y avoir aucun moyen de le faire. Existe-t-il un getTimeout fonction que j'ai manquée ? Les seules informations sur les délais d'attente en Javascript que j'ai pu trouver concernent uniquement la création via setTimeout( function, time) et la suppression via clearTimeout( id ) .

Je cherche une fonction qui renvoie soit le temps restant avant le déclenchement d'un délai d'attente, soit le temps écoulé après le déclenchement d'un délai d'attente. Le code de ma barre 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. Je suis passé par la section de la bibliothèque qui est en charge des tests, et j'ai débrouillé le code (terrible, et contre mes permissions).

// 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 for setTimeout
function mySetTimeout( func, timeout ) {
    timeouts\[ n = setTimeout( func, timeout ) \] = {
        start: new Date().getTime(),
        end: new Date().getTime() + timeout
        t: timeout
    }
    return n;
}

Cela fonctionne parfaitement dans tous les navigateurs qui ne sont pas IE 6. Même l'iPhone original, où je m'attendais à ce que les choses deviennent asynchrones.

63voto

Fluffy Points 6908

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

Imprimés :

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

Cela ne semble pas fonctionner dans firefox par, mais puisque node.js est javascript, j'ai pensé que cette remarque pourrait être utile pour les personnes qui cherchent la solution node.

5 votes

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 soit déjà un entier maintenant.

3 votes

Je viens de l'essayer dans le nœud 0.10, getTime() est supprimé, _idleStart est déjà le temps

2 votes

Ne fonctionne pas sur le noeud 6.3, parce que la structure timeout est une fuite de ces informations.

57voto

Murplyx Points 455

EDIT : En fait, je pense que j'en ai fait une encore meilleure : 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()
}

Faites une minuterie :

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

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

a.getTimeLeft()

0 votes

Merci pour cela. Ce n'est pas exactement ce que je cherchais, mais la fonction permettant de compter le temps restant est très utile.

31voto

lawnsea Points 2226

Si vous ne pouvez pas modifier le code de la bibliothèque, vous devrez redéfinir setTimeout pour l'adapter à 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);

Il ne s'agit pas d'un code de production, mais cela devrait vous mettre sur la bonne voie. Notez que vous ne pouvez lier qu'un seul listener 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'enveloppement de setTimeout, mais utiliserait plutôt une correspondance entre le timeoutId retourné et les listeners pour gérer plusieurs listeners par timeout. Vous pourriez également envisager d'intégrer clearTimeout afin de pouvoir détacher votre écouteur si le délai d'attente est supprimé.

0 votes

Merci, vous avez sauvé ma journée !

15 votes

En raison du temps d'exécution, il peut dériver de quelques millisecondes au fil du temps. Une façon plus sûre de procéder consiste à enregistrer l'heure à laquelle vous avez lancé le délai d'attente (new Date()), puis à la soustraire de l'heure actuelle (new Date()).

4voto

vol7ron Points 11270

Les piles d'événements de Javascript ne fonctionnent pas comme on pourrait le croire.

Lorsqu'un événement de dépassement de délai est créé, il est ajouté à la file d'attente des événements, mais d'autres événements peuvent prendre la priorité pendant l'exécution de cet événement, ce qui retarde le temps d'exécution et repousse le moment de l'exécution.

Exemple : Vous créez une temporisation avec un délai de 10 secondes pour alerter l'écran. Il sera ajouté à la pile d'événements et sera exécuté après le déclenchement de tous les événements courants (ce qui entraîne un certain délai). Ensuite, lorsque le délai d'attente est traité, le navigateur continue à capturer d'autres événements et à les ajouter à la pile, ce qui entraîne des retards supplémentaires dans le traitement. Si l'utilisateur clique, ou fait beaucoup de ctrl+tape, leurs événements prennent la priorité sur la pile actuelle. Vos 10 secondes peuvent se transformer en 15 secondes, voire plus.


Cela dit, il existe de nombreuses façons de simuler le temps qui passe. L'une d'elles consiste à exécuter un setInterval juste après avoir ajouté le setTimeout à la pile.

Exemple : Effectuez un settimeout avec un délai de 10 secondes (stockez ce délai dans un global). Ensuite, effectuez un setInterval qui s'exécute toutes les secondes pour soustraire 1 au 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), ce n'est toujours pas précis, mais cela donne un compte.


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

0voto

galambalazs Points 24393

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

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

0 votes

La différence ne peut être mesurée qu'en ms, car l'animation commence au début de votre fonction. J'ai vu que vous utilisez jQuery. Vous pouvez utiliser jquery.animate alors.

0 votes

Le meilleur moyen est d'essayer et de voir par vous-même :)

Prograide.com

Prograide est une communauté de développeurs qui cherche à élargir la connaissance de la programmation au-delà de l'anglais.
Pour cela nous avons les plus grands doutes résolus en français et vous pouvez aussi poser vos propres questions ou résoudre celles des autres.

Powered by:

X