2010-06-29 7 views
67

Estoy escribiendo un código JavaScript que interactúa con el código de la biblioteca que no soy de mi propiedad y no puedo (razonablemente) cambiar. Crea tiempos de espera de Javascript utilizados 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 la biblioteca está haciendo:encuentra el tiempo restante en un setTimeout()?

.... 
// 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 llena hacia questionTime * 1000 interrogando el temporizador creado por setTimeout. El único problema es que parece que no hay forma de hacer esto. ¿Hay una función getTimeout que me falta? La única información sobre los tiempos de espera de Javascript que puedo encontrar está relacionada solo con la creación a través del setTimeout(function, time) y la eliminación a través del clearTimeout(id).

Estoy buscando una función que devuelva el tiempo restante antes de que se agote el tiempo de espera o el tiempo transcurrido después de que se haya invocado un tiempo de espera. Mi código de barra 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 puedo encontrar el tiempo restante antes de que un setTimeout() de JavaScript?


Aquí está la solución que estoy usando ahora. Pasé por 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:

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

Esto funciona bastante en el clavo en cualquier navegador que no es Internet Explorer 6. Incluso el iPhone original, donde esperaba cosas para obtener asíncrono.

Respuesta

24

Si no puede modificar el código de la biblioteca, deberá redefinir setTimeout para que se ajuste a sus propósitos. He aquí un ejemplo de lo que podría 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 el código de producción, sino que se debe poner en el camino correcto. Tenga en cuenta que solo puede enlazar un oyente por timeout. No he hecho pruebas exhaustivas con esto, pero funciona en Firebug.

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

+0

Gracias ¡Me salvaste el día! – xecutioner

+9

Debido al tiempo de ejecución, esto puede derivar en unos pocos milisegundos durante un tiempo. Una forma más segura de hacerlo es registrar el momento en que comenzó el tiempo de espera (nueva fecha()) y luego restarlo de la hora actual (otra fecha nueva()) –

0

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

Digamos que su pregunta es el siguiente:

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

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

Y la animación estará a cargo de 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); 
} 
+0

bien la diferencia sólo puede medirse en ms, debido a que la animación se inicia en la parte superior de su función. Vi que usaste jQuery. Puedes usar jquery.animate entonces. – galambalazs

+0

La mejor manera es tratar de verlo por ti mismo. :) – galambalazs

3

pilas de eventos de Javascript no operan cómo se podría pensar.

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

Ejemplo: Se crea un tiempo de espera con un retraso de 10 segundos para alertar algo a la pantalla. Se agregará a la pila de eventos y se ejecutará después de que se activen todos los eventos actuales (lo que ocasiona cierta demora). Luego, cuando se procesa el tiempo de espera, el navegador 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 escribe mucho ctrl +, sus eventos tienen prioridad sobre la pila actual. Sus 10 segundos pueden convertirse en 15 segundos o más.


Habiendo dicho eso, hay muchas maneras de simular cuánto tiempo ha pasado.Una forma es ejecutar un setInterval justo después de agregar el setTimeout a la pila.

Ejemplo: Realice una setTimeout con un segundo retardo 10 (la tienda que la demora en una global). A continuación, realice un setInterval que se ejecuta cada segundo para restar 1 del retraso y dar salida a la demora restante. Debido a la forma en que la pila de eventos puede influir en el tiempo real (descrito anteriormente), esto aún no será preciso, pero da un conteo.


En resumen, no hay una manera real de obtener el tiempo restante. Solo hay formas de intentar y transmitir una estimación al usuario.

45

Sólo para que conste, no es una manera de conseguir el tiempo que queda 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); 
} 

Lienzo:

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

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

+4

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 entero ahora – kasztelan

+3

Lo acabo de probar en el nodo 0.10, 'getTime()' se eliminó, '_idleStart' ya es el tiempo –

+1

que no funciona en el nodo 6.3, porque la estructura de tiempo de espera es la fuga de esa información. – zixia

0

Si alguien está mirando hacia atrás en esto. He salido con un gestor de tiempo de espera e intervalo que puede obtener el tiempo restante en un tiempo de espera o intervalo, así como también hacer otras cosas. Voy a añadir a ella para que sea más ingenioso y más preciso, pero parece que funciona bastante bien como es (aunque no tengo más ideas para que sea aún más precisa):

https://github.com/vhmth/Tock

+0

sus enlaces están rotos –

+0

@KickButtowski actualizado – Vinay

36

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

escribí esta función y lo 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() 
} 

Hacer un temporizador:

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

lo tanto, si desea que el tiempo restante acaba de hacer:

a.getTimeLeft() 
+0

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

+5

Agradable y elegante. –

+0

Perfecto, gracias! – tolmark

0

Aquí es tal vez una mejor manera de hacerlo, además, no se necesita cambiar el código que ya ha escrito:

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

...Y mimetizado:

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

La pregunta ya ha sido respondida, pero añadiré mi parte. Simplemente se me ocurrió.

Uso setTimeout en recursion de la siguiente 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í

0

comprobar éste:

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

Hacer un temporizador:

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

Obtener siendo:

smth.get() 

Borrar 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); 
Cuestiones relacionadas