encontrar el tiempo restante en un setTimeout ()?


90

Estoy escribiendo algo de Javascript que interactúa con el código de la biblioteca que no soy de mi propiedad y que no puedo (razonablemente) cambiar. Crea tiempos de espera de Javascript que se utilizan para mostrar la siguiente pregunta en una serie de preguntas de tiempo limitado. Este no es un código real porque está ofuscado más allá de toda esperanza. Esto es lo que está haciendo la biblioteca:

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

Quiero poner una barra de progreso en pantalla que se llene questionTime * 1000al interrogar el temporizador creado por setTimeout. El único problema es que parece que no hay forma de hacer esto. ¿Hay una getTimeoutfunción que me falta? La única información sobre los tiempos de espera de Javascript que puedo encontrar está relacionada únicamente con la creación setTimeout( function, time)y eliminación mediante clearTimeout( id ).

Estoy buscando una función que devuelva el tiempo restante antes de que se active un tiempo de espera o el tiempo transcurrido después de que se haya llamado a un tiempo de espera. Mi código de barras de progreso se ve así:

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: ¿Cómo encuentro el tiempo restante antes de un setTimeout () de javascript?


Aquí está la solución que estoy usando ahora. Revisé la sección de la biblioteca que está a cargo de las pruebas y descifré el código (terrible y en contra de mis permisos).

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

y aquí está mi código:

// contenedor para setTimeout
function mySetTimeout (func, timeout) {
    tiempos de espera [n = setTimeout (func, timeout)] = {
        inicio: nueva fecha (). getTime (),
        fin: nueva fecha (). getTime () + tiempo de espera
        t: tiempo de espera
    }
    return n;
}

Esto funciona bastante bien en cualquier navegador que no sea IE 6. Incluso en el iPhone original, donde esperaba que las cosas se volvieran asincrónicas.

Respuestas:


31

Si no puede modificar el código de la biblioteca, deberá redefinir setTimeout para que se adapte a sus propósitos. Aquí tienes un ejemplo de lo que podrías hacer:

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

Este no es un código de producción, pero debería ponerlo en el camino correcto. Tenga en cuenta que solo puede vincular un oyente por tiempo de espera. No he realizado pruebas exhaustivas con esto, pero funciona en Firebug.

Una solución más robusta usaría la misma técnica de empaquetar setTimeout, pero en su lugar usaría un mapa del timeoutId devuelto a los oyentes para manejar múltiples oyentes por tiempo de espera. También puede considerar envolver clearTimeout para que pueda desconectar su oyente si se borra el tiempo de espera.


Gracias ¡Me salvaste el día!
xecutioner

14
Debido al tiempo de ejecución, esto puede variar en algunos milisegundos durante un tiempo. Una forma más segura de hacer esto es registrar la hora en que comenzó el tiempo de espera (nueva Fecha ()) y luego restarla de la hora actual (otra nueva Fecha ())
Jonathan

61

Solo para que conste, hay una manera de obtener el tiempo restante en 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);
}

Huellas dactilares:

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

Esto no parece funcionar en Firefox, pero como node.js es javascript, pensé que este comentario podría ser útil para las personas que buscan la solución de nodo.


5
No estoy seguro si es una nueva versión del nodo o qué, pero para que esto funcione, tuve que eliminar la parte "getTime ()". parece que _idleStart ya es un número entero ahora
kasztelan

3
Lo acabo de probar en el nodo 0.10, getTime()se elimina, _idleStartya es el momento
Tan Nguyen

2
no funciona en el nodo 6.3, porque la estructura de tiempo de espera es una fuga de esa información.
Huan

53

EDITAR: De hecho, creo que hice uno aún mejor: https://stackoverflow.com/a/36389263/2378102

Escribí esta función y la uso mucho:

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

Haz un temporizador:

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

Entonces, si quieres el tiempo restante, haz lo siguiente:

a.getTimeLeft()

Gracias por eso. No es exactamente lo que estaba buscando, pero la función para contar el tiempo restante es muy útil
cambio,

4

Las pilas de eventos de Javascript no funcionan como usted pensaría.

Cuando se crea un evento de tiempo de espera, se agrega a la cola de eventos, pero otros eventos pueden tener prioridad mientras se activa ese evento, retrasar el tiempo de ejecución y posponer el tiempo de ejecución.

Ejemplo: crea un tiempo de espera con un retraso de 10 segundos para alertar algo en la pantalla. Se agregará a la pila de eventos y se ejecutará después de que se activen todos los eventos actuales (causando cierto retraso). Luego, cuando se procesa el tiempo de espera, el navegador aún continúa capturando otros eventos y los agrega a la pila, lo que causa más retrasos en el procesamiento. Si el usuario hace clic o hace mucho ctrl + tipeo, sus eventos tienen prioridad sobre la pila actual. Tus 10 segundos pueden convertirse en 15 segundos o más.


Dicho esto, hay muchas formas de fingir cuánto tiempo ha pasado. Una forma es ejecutar setInterval justo después de agregar setTimeout a la pila.

Ejemplo: realizar un tiempo de espera establecido con un retraso de 10 segundos (almacenar ese retraso en un global) Luego, realice un setInterval que se ejecute cada segundo para restar 1 del retraso y generar el retraso restante. Debido a cómo la pila de eventos puede influir en el tiempo real (descrito anteriormente), esto aún no será exacto, pero da un recuento.


En resumen, no hay una forma real de obtener el tiempo restante. Solo hay formas de intentar transmitir un presupuesto al usuario.


4

Específico de Node.js del lado del servidor

Nada de lo anterior realmente funcionó para mí, y después de inspeccionar el objeto de tiempo de espera, parecía que todo era relativo a cuando comenzó el proceso. Lo siguiente funcionó para mí:

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

Salida:

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

node -v
v9.11.1

Salida editada por brevedad, pero esta función básica da un tiempo aproximado hasta la ejecución o desde la ejecución. Como mencionan otros, nada de esto será exacto debido a la forma en que procesa el nodo, pero si quiero suprimir una solicitud que se ejecutó hace menos de 1 minuto y guardé el temporizador, no veo por qué esto no lo haría. funciona como una comprobación rápida. Podría ser interesante hacer malabarismos con objetos con refreshtimer en 10.2+.


1

Puede modificar setTimeout para almacenar la hora de finalización de cada tiempo de espera en un mapa y crear una función llamadagetTimeout para obtener el tiempo restante para un tiempo de espera con una determinada identificación.

Esta fue la solución de Super , pero la modifiqué para usar un poco menos de 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;
    }
})();

Uso:

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

Aquí hay una versión reducida de este 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})();

¡Espero que esto sea tan útil para ti como lo fue para mí! :)


0

No, pero puede tener su propio setTimeout / setInterval para la animación en su función.

Digamos que su pregunta se ve así:

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

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

Y su animación será manejada por estas 2 funciones:

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

bueno, la diferencia solo se puede medir en ms, porque la animación comienza en la parte superior de su función. Te vi usar jQuery. Entonces puedes usar jquery.animate.
gblazex

La mejor manera es probarlo y verlo por ti mismo. :)
gblazex

0

Si alguien está mirando hacia atrás en esto. He creado un administrador de tiempo muerto e intervalo que puede darte el tiempo restante en un tiempo muerto o intervalo, además de hacer algunas otras cosas. Lo agregaré para hacerlo más ingenioso y preciso, pero parece funcionar bastante bien tal como está (aunque tengo algunas ideas más para hacerlo aún más preciso):

https://github.com/vhmth/Tock


sus enlaces están rotos
Kick Buttowski

0

La pregunta ya ha sido respondida pero agregaré mi granito de arena. Simplemente se me ocurrió.

Úselo setTimeoutde la recursionsiguiente manera:

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

Supongo que el código se explica por sí mismo.


0

Revisa este:

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

Haz un temporizador:

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

Obtener permanecer:

smth.get()

Limpiar el tiempo de espera

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

Pasé por aquí buscando esta respuesta, pero estaba pensando demasiado en mi problema. Si está aquí porque solo necesita realizar un seguimiento del tiempo mientras está configuradoTimeout está en progreso, aquí hay otra forma de hacerlo:

    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

Una forma más rápida y sencilla:

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

Puede obtener el tiempo restante con:

 timeLeft = tmo - (performance.now() - start);
Al usar nuestro sitio, usted reconoce que ha leído y comprende nuestra Política de Cookies y Política de Privacidad.
Licensed under cc by-sa 3.0 with attribution required.