Como criar um timer preciso em javascript?

Eu preciso criar um timer simples, mas preciso.

Este é o meu código:

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

Após exatamente 3600 segundos, imprime cerca de 3500 segundos.

  • Por que não é preciso?

  • Como posso criar um timer preciso?

Por que não é preciso?

Porque você está usando setTimeout() ou setInterval() . Eles não são confiáveis , não há garantias de precisão para eles. Eles podem ficar arbitrariamente, e não mantêm um ritmo constante, mas tendem a se desviar (como você observou).

Como posso criar um timer preciso?

Use o object Date para obter o tempo atual exato (milissegundo). Em seguida, baseie sua lógica no valor de hora atual, em vez de contar com que frequência seu retorno de chamada foi executado.

Para um timer ou relógio simples, acompanhe a diferença de horário explicitamente:

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

Agora, isso tem o problema de possivelmente saltar valores. Quando o intervalo fica um pouco e executa o retorno de chamada após 990 , 1993 , 2996 , 3999 , 5002 milissegundos, você verá a segunda contagem 0 , 1 , 2 , 3 , 5 (!). Por isso, seria aconselhável actualizar mais frequentemente, como a cada 100 ms, para evitar esses saltos.

No entanto, às vezes você realmente precisa de um intervalo constante executando seus retornos de chamada sem drifting. Isso requer uma estratégia um pouco mais vantajosa (e código), apesar de pagar bem (e registrar menos tempos limite). Esses são conhecidos como timeres auto-ajustáveis . Aqui, o atraso exato de cada um dos tempos limite repetidos é adaptado ao tempo efetivamente decorrido, comparado aos 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 } 

Eu sou apenas construir sobre a resposta de Bergi (especificamente a segunda parte) um pouco, porque eu realmente gostei do jeito que foi feito, mas eu quero a opção de parar o timer, uma vez iniciado (como clearInterval() quase). Sooo … Eu o envolvi em uma function de construtor para que possamos fazer coisas “objetivas” com ele.

1. Construtor

Tudo bem, então copie / cole isso …

 /** * 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) - This * @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. Instanciar

Diga-lhe o que fazer e tudo o que …

 // 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. Então faça … coisas

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

Quero dizer, funciona para mim de qualquer maneira. Se há um jeito melhor, deixa eu saber.

Não fica muito mais preciso do que isso.

 var seconds = new Date().getTime(), last = seconds, intrvl = setInterval(function() { var now = new Date().getTime(); if(now - last > 5){ if(confirm("Delay registered, terminate?")){ clearInterval(intrvl); return; } } last = now; timer.innerHTML = now - seconds; }, 333); 

Quanto ao motivo pelo qual não é preciso, eu suponho que a máquina está ocupada fazendo outras coisas, diminuindo um pouco a cada iteração, como você vê.

Eu concordo com Bergi em usar Date, mas sua solução foi um pouco exagerada para meu uso. Eu simplesmente queria que o meu relógio animado (SVGs digital e analógico) fosse atualizado no segundo e não sobrecarregado ou em execução, criando saltos óbvios nas atualizações do relógio. Aqui está o trecho de código que eu coloquei nas minhas funções de atualização do relógio:

  var milliseconds = now.getMilliseconds(); var newTimeout = 1000 - milliseconds; this.timeoutVariable = setTimeout((function(thisObj) { return function() { thisObj.update(); } })(this), newTimeout); 

Ele simplesmente calcula o tempo delta para o próximo segundo, e define o tempo limite para esse delta. Isso sincroniza todos os meus objects de relógio para o segundo. Espero que isso seja útil.