¿Cómo crear un temporizador preciso en javascript?

Resuelto xRobot asked hace 9 años • 16 respuestas

Necesito crear un temporizador simple pero preciso.

Este es mi código:

var seconds = 0;
setInterval(function() {
timer.innerHTML = seconds++;
}, 1000);

Después de exactamente 3600 segundos, imprime unos 3500 segundos.

  • ¿Por qué no es exacto?

  • ¿Cómo puedo crear un cronómetro preciso?

xRobot avatar Apr 30 '15 22:04 xRobot
Aceptado

¿Por qué no es exacto?

Porque estás usando setTimeout()o setInterval(). No se puede confiar en ellos , no hay garantías de precisión para ellos. Se les permite retrasarse arbitrariamente y no mantienen un ritmo constante sino que tienden a desviarse (como usted ha observado).

¿Cómo puedo crear un cronómetro preciso?

Utilice el Dateobjeto en su lugar para obtener la hora actual con precisión (milisegundos). Luego base su lógica en el valor de tiempo actual, en lugar de contar con qué frecuencia se ejecutó su devolución de llamada.

Para un temporizador o reloj simple, realice un seguimiento de la diferencia horaria explícitamente:

var start = Date.now();
setInterval(function() {
    var delta = Date.now() - start; // milliseconds elapsed since startoutput(Math.floor(delta / 1000)); // in seconds
    // alternatively just show wall clock time:
    output(new Date().toUTCString());
}, 1000); // update about every second

Ahora bien, eso tiene el problema de posibles saltos de valores. Cuando el intervalo se retrasa un poco y ejecuta su devolución de llamada después de ,,,,, milisegundos , 990verá el segundo conteo ,,,,, ( ! ). Por lo tanto, sería recomendable actualizar con más frecuencia, aproximadamente cada 100 ms, para evitar tales saltos.199329963999500201235

Sin embargo, a veces realmente necesitas un intervalo constante para ejecutar tus devoluciones de llamada sin desviarte. Esto requiere una estrategia (y código) un poco más avanzados, aunque da buenos resultados (y registra menos tiempos de espera). Se conocen como temporizadores autoajustables . Aquí el retraso exacto para cada uno de los tiempos de espera repetidos se adapta al tiempo realmente transcurrido, en comparación con los intervalos esperados:

var interval = 1000; // ms
var expected = Date.now() + interval;
setTimeout(step, interval);
function step() {
    var dt = Date.now() - expected; // the drift (positive for overshooting)
    if (dt > interval) {
        // something really bad happened. Maybe the browser (tab) was inactive?
        // possibly special handling to avoid futile "catch up" run
    }
    … // do what is to be done

    expected += interval;
    setTimeout(step, Math.max(0, interval - dt)); // take into account drift
}
Bergi avatar Apr 30 '2015 15:04 Bergi

Me basaré un poco en la respuesta de Bergi (específicamente la segunda parte) porque realmente me gustó la forma en que se hizo, pero quiero tener la opción de detener el cronómetro una vez que comience (como clearInterval()casi). Así que... lo he incluido en una función constructora para que podamos hacer cosas 'objetivas' con él.

1. Constructor

Muy bien, entonces copias y pegas eso...

/**
 * Self-adjusting interval to account for drifting
 * 
 * @param {function} workFunc  Callback containing the work to be done
 *                             for each interval
 * @param {int}      interval  Interval speed (in milliseconds)
 * @param {function} errorFunc (Optional) Callback to run if the drift
 *                             exceeds interval
 */
function AdjustingInterval(workFunc, interval, errorFunc) {
    var that = this;
    var expected, timeout;
    this.interval = interval;

    this.start = function() {
        expected = Date.now() + this.interval;
        timeout = setTimeout(step, this.interval);
    }

    this.stop = function() {
        clearTimeout(timeout);
    }

    function step() {
        var drift = Date.now() - expected;
        if (drift > that.interval) {
            // You could have some default stuff here too...
            if (errorFunc) errorFunc();
        }
        workFunc();
        expected += that.interval;
        timeout = setTimeout(step, Math.max(0, that.interval-drift));
    }
}

2. Crear una instancia

Dile qué hacer y todo eso...

// For testing purposes, we'll just increment
// this and send it out to the console.
var justSomeNumber = 0;

// Define the work to be done
var doWork = function() {
    console.log(++justSomeNumber);
};

// Define what to do if something goes wrong
var doError = function() {
    console.warn('The drift exceeded the interval.');
};

// (The third argument is optional)
var ticker = new AdjustingInterval(doWork, 1000, doError);

3. Entonces haz... cosas

// You can start or stop your timer at will
ticker.start();
ticker.stop();

// You can also change the interval while it's in progress
ticker.interval = 99;

Quiero decir, a mí me funciona de todos modos. Si hay una manera mejor, déjamelo saber.

Leon Williams avatar Jun 02 '2017 21:06 Leon Williams