web-dev-qa-db-fra.com

trouver le temps restant dans un setTimeout ()?

J'écris du code Javascript qui interagit avec le code de bibliothèque que je ne possède pas et ne peut pas (raisonnablement) changer. 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 un code réel 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 chronomètre créé par setTimeout. Le seul problème est qu'il semble n'y avoir aucun moyen de le faire. Existe-t-il une fonction getTimeout qui me manque? La seule information sur les délais d'attente Javascript que je peux trouver concerne uniquement la création via setTimeout( function, time) et la suppression via clearTimeout( id ).

Je recherche une fonction qui renvoie le temps restant avant l’expiration d’un délai ou le temps écoulé après l’appel d’un délai. 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 setTimeout () javascript?


Voici la solution que j'utilise maintenant. J'ai parcouru la section de la bibliothèque chargée des tests, et j'ai déchiffré le code (horrible, 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 pour setTimeout 
 function mySetTimeout (func, timeout) {
 délais d'attente [n = setTimeout (func, délai d'attente)] = {
 start: new Date (). getTime (), 
 end: new Date (). getTime () + délai d'attente 
 t: timeout 
 } 
 retourne n; 
}

Cela fonctionne assez bien sur 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.

78
Just Jake

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 un code de production, mais cela devrait vous mettre sur la bonne voie. Notez que vous ne pouvez lier qu'un seul auditeur par délai d'attente. Je n'ai pas fait de tests approfondis avec cela, mais cela fonctionne dans Firebug.

Une solution plus robuste utiliserait la même technique pour encapsuler setTimeout, mais utiliserait plutôt une mappe de timeoutId renvoyée aux écouteurs pour gérer plusieurs écouteurs par délai d'attente. Vous pouvez également envisager d’emballer clearTimeout afin de pouvoir détacher votre auditeur si le délai d’attente est effacé.

25
lawnsea

Pour mémoire, il existe un moyen de récupérer 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 puisque node.js est javascript, je pensais que cette remarque pourrait être utile aux personnes recherchant la solution de noeud.

49
Fluffy

EDIT: En fait, je pense en avoir fait un 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 qui reste, faites juste:

a.getTimeLeft()
44
super

Voici peut-être une façon encore meilleure de le faire. De plus, vous n'aurez pas besoin de changer le code que vous avez déjà écrit:

var getTimeout = (function() { // IIFE
    var _setTimeout = setTimeout, // Reference to the original setTimeout
        map = {}; // Map of all timeouts with their start date and delay

    setTimeout = function(callback, delay) { // Modify setTimeout
        var id = _setTimeout(callback, delay); // Run the original, and store the id

        map[id] = [Date.now(), delay]; // Store the start date and delay

        return id; // Return the id
    };

    return function(id) { // The actual getTimeLeft function
        var m = map[id]; // Find the timeout in map

        // If there was no timeout with that id, return NaN, otherwise, return the time left clamped to 0
        return m ? Math.max(m[1] - Date.now() + m[0], 0) : NaN;
    }
})();

... et mimimés:

var getTimeout=function(){var e=setTimeout,b={};setTimeout=function(a,c){var d=e(a,c);b[d]=[Date.now(),c];return d};return function(a){return(a=b[a])?Math.max(a[1]-Date.now()+a[0],0):NaN}}();
3
super

Les piles d'événements 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 des événements, mais d'autres événements peuvent être prioritaires pendant le déclenchement de cet événement, retarder l'heure d'exécution et retarder l'exécution. 

Exemple:Vous créez un délai d'attente avec un délai de 10 secondes pour alerter quelque chose sur l'écran. Il sera ajouté à la pile d'événements et sera exécuté une fois que tous les événements actuels auront été déclenchés (ce qui causera un certain retard). Ensuite, lorsque le délai d'attente est traité, le navigateur continue de capturer d'autres événements, de les ajouter à la pile, ce qui entraîne des retards supplémentaires dans le traitement. Si l'utilisateur clique sur, ou fait beaucoup de ctrl + frappe, leurs événements sont prioritaires sur la pile actuelle. Vos 10 secondes peuvent se transformer en 15 secondes ou plus. 


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

Exemple:Effectue un settimeout avec un délai de 10 secondes (enregistrez ce délai dans un délai global). Effectuez ensuite un setInterval qui s'exécute toutes les secondes pour soustraire 1 du délai et générer 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 exact, mais donnera un compte.


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

3
vol7ron

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

Aucune de ce qui précède n'a vraiment fonctionné pour moi, et après avoir inspecté l'objet de délai d'attente, tout semblait être relatif au début 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);

Sortie:

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

node -v
v9.11.1

Édition de la sortie par souci de brièveté, mais cette fonction de base donne un temps approximatif jusqu'à l'exécution ou depuis l'exécution. Comme d'autres l'ont mentionné, rien de tout cela ne sera exact à cause de la façon dont le noeud traite les processus, mais si je veux supprimer une demande qui a été exécutée il y a moins d'une minute et que j'ai enregistré le chronomètre, je ne vois pas pourquoi cela ne se produirait pas. travailler comme un contrôle rapide. Pourrait être intéressant de jongler avec des objets avec refreshtimer dans 10.2+.

2
John Watts

La question a déjà été répondue mais je vais ajouter mon bit. Cela m'est juste arrivé.

Utilisez setTimeout dans recursion comme suit:

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

0
dasfdsa

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

Obtenir reste:

smth.get()

Effacer le délai

smth.clear()
0
crokudripi

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

Je me suis arrêté ici pour chercher cette réponse, mais je pensais trop à mon problème. Si vous êtes ici parce que vous avez juste besoin de garder le temps alors que vous êtes en train de définir Timeout 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);
0
Jbur43

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

C'était super 's solution , mais je l'ai modifié 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 abrégée de cette getTimeoutIIFE :

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 est aussi utile que cela l'a été pour moi! :)

0
Kimeiga

Si quelqu'un regarde en arrière sur ceci. Je suis arrivé avec un gestionnaire de délai d'attente et d'intervalle qui peut vous rapporter le temps restant dans un délai d'attente ou un intervalle, ainsi que d'autres tâches. J'ajouterai quelque chose pour le rendre plus astucieux et précis, mais cela semble fonctionner assez bien tel quel (bien que j'aie d'autres idées pour le rendre encore plus précis):

https://github.com/vhmth/Tock

0
Vinay
    (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
Vitalik s2pac