Esiste un modo più accurato per creare un timer Javascript rispetto a setTimeout?

Qualcosa che mi ha sempre infastidito è quanto imprevedibile sia il metodo setTimeout() in Javascript.

Nella mia esperienza, il timer è orribilmente inaccurato in molte situazioni. Per inesatto, intendo che il tempo di ritardo effettivo sembra variare di 250-500 ms in più o in meno. Anche se non è un’enorme quantità di tempo, quando lo si usa per hide / mostrare elementi dell’interfaccia utente, il tempo può essere visibilmente notevole.

Ci sono trucchi che possono essere fatti per assicurare che setTimeout() funzioni con precisione (senza ricorrere a un’API esterna) o è una causa persa?

Ci sono trucchi che possono essere fatti per assicurare che setTimeout() funzioni con precisione (senza ricorrere a un’API esterna) o è una causa persa?

No e no Non avrai nulla vicino a un timer perfettamente preciso con setTimeout() – i browser non sono impostati per questo. Tuttavia , non è necessario affidarsi a questo per le cose di temporizzazione. La maggior parte delle librerie di animazioni lo ha capito anni fa: si imposta una richiamata con setTimeout() , ma si determina cosa deve essere fatto in base al valore di (new Date()).milliseconds (o equivalente). Ciò consente di usufruire di un supporto timer più affidabile nei nuovi browser, pur continuando a comportarsi in modo appropriato sui browser meno recenti.

Ti permette anche di evitare di usare troppi timer ! Questo è importante: ogni timer è un callback. Ogni callback esegue il codice JS. Mentre il codice JS è in esecuzione, gli eventi del browser, inclusi altri callback, vengono ritardati o eliminati. Al termine della richiamata, i callback aggiuntivi devono competere con altri eventi del browser per poter essere eseguiti. Pertanto, un timer che gestisce tutte le attività in sospeso per quell’intervallo funzionerà meglio di due timer con intervalli coincidenti e (per brevi timeout) migliore di due timer con timeout sovrapposti!

Riepilogo: smetti di usare setTimeout() per implementare i progetti “one timer / one task” e usa l’orologio in tempo reale per appianare le azioni dell’interfaccia utente.

.

REF; http://www.sitepoint.com/creating-accurate-timers-in-javascript/

Questo sito mi ha salvato su larga scala.

È ansible utilizzare l’orologio di sistema per compensare l’inesattezza del timer. Se si esegue una funzione di temporizzazione come una serie di chiamate setTimeout – ogni istanza che chiama il successivo – allora tutto ciò che si deve fare per mantenerlo accurato è capire esattamente quanto è impreciso e sottrarre tale differenza dall’iterazione successiva:

 var start = new Date().getTime(), time = 0, elapsed = '0.0'; function instance() { time += 100; elapsed = Math.floor(time / 100) / 10; if(Math.round(elapsed) == elapsed) { elapsed += '.0'; } document.title = elapsed; var diff = (new Date().getTime() - start) - time; window.setTimeout(instance, (100 - diff)); } window.setTimeout(instance, 100); 

Questo metodo ridurrà al minimo la deriva e ridurrà le imprecisioni di oltre il 90%.

Ha risolto i miei problemi, spero che aiuti

Ho avuto un problema simile non molto tempo fa e ho trovato un approccio che combina requestAnimationFrame con performance.now () che funziona in modo molto efficace.

Ora sono in grado di impostare i timer con una precisione di circa 12 cifre decimali:

  window.performance = window.performance || {}; performance.now = (function() { return performance.now || performance.mozNow || performance.msNow || performance.oNow || performance.webkitNow || function() { //Doh! Crap browser! return new Date().getTime(); }; })(); 

http://jsfiddle.net/CGWGreen/9pg9L/

La risposta di Shog9 è più o meno quello che direi, anche se aggiungerei quanto segue sull’animazione / eventi dell’interfaccia utente:

Se hai una scatola che dovrebbe scorrere sullo schermo, espandi verso il basso, quindi dissolvenza nel suo contenuto, non provare a rendere tutti e tre gli eventi separati con ritardi temporizzati per farli sparare uno dopo l’altro – usa i callback, quindi una volta il primo evento è fatto facendo scorrere chiama l’expander, una volta fatto chiama il fader. jQuery può farlo facilmente, e sono sicuro che anche altre librerie possono farlo.

Se hai bisogno di ottenere una callback accurata in un dato intervallo, questo elenco potrebbe aiutarti a:

https://gist.github.com/1185904

Se si utilizza setTimeout() per ottenere rapidamente il browser in modo che il thread dell’interfaccia utente possa recuperare le attività che deve eseguire (ad esempio l’aggiornamento di una scheda o la mancata visualizzazione della finestra di dialogo Scorri lunghi), è disponibile una nuova API chiamato Efficient Script Yielding , ovvero setImmediate() che potrebbe funzionare un po ‘meglio per te.

setImmediate() opera in modo molto simile a setTimeout() , ma può essere eseguito immediatamente se il browser non ha altro da fare. In molte situazioni in cui si utilizza setTimeout(..., 16) o setTimeout(..., 4) o setTimeout(..., 0) (vale a dire che si desidera che il browser esegua attività di thread dell’interfaccia utente in sospeso e non mostri un Finestra di dialogo setImmediate() lunghi), puoi semplicemente sostituire il tuo setTimeout() con setImmediate() , lasciando cadere il secondo argomento (millisecondo).

La differenza con setImmediate() è che è fondamentalmente un rendimento; se il browser ha a volte da fare sul thread dell’interfaccia utente (ad esempio, aggiorna una scheda), lo farà prima di tornare al callback. Tuttavia, se il browser è già stato interamente coinvolto nel suo lavoro, la funzione di callback specificata in setImmediate() verrà eseguita senza alcun ritardo.

Sfortunatamente è attualmente supportato solo in IE9 +, in quanto vi è un po ‘di distacco dagli altri produttori di browser.

Tuttavia, è disponibile un buon polyfill , se si desidera utilizzarlo e sperare che gli altri browser lo implementino a un certo punto.

Se si utilizza setTimeout() per l’animazione , requestAnimationFrame è la soluzione migliore poiché il codice verrà eseguito in sincrono con la frequenza di aggiornamento del monitor.

Se si utilizza setTimeout() su una cadenza più lenta , ad esempio una volta ogni 300 millisecondi, è ansible utilizzare una soluzione simile a quella suggerita dall’utente1213320, dove si controlla quanto tempo era dall’ultimo timestamp eseguito il timer e si compensa qualsiasi ritardo. Un miglioramento è che è ansible utilizzare la nuova interfaccia High Resolution Time (alias window.performance.now() ) anziché Date.now() per ottenere una risoluzione superiore al millisecondo per l’ora corrente.

Hai bisogno di “strisciare” sul tempo target. Alcuni tentativi ed errori saranno necessari, ma in sostanza.

Impostare un timeout per completare 100 ms intorno al tempo richiesto

rendere la funzione di gestione del timeout in questo modo:

 calculate_remaining_time if remaining_time > 20ms // maybe as much as 50 re-queue the handler for 10ms time else { while( remaining_time > 0 ) calculate_remaining_time; do_your_thing(); re-queue the handler for 100ms before the next required time } 

Ma il tuo ciclo temporale può ancora essere interrotto da altri processi, quindi non è ancora perfetto.

Ecco un esempio dimostrativo del suggerimento di Shog9. Questo riempie una barra di avanzamento jquery in modo uniforms per 6 secondi, quindi reindirizza a una pagina diversa una volta riempita:

 var TOTAL_SEC = 6; var FRAMES_PER_SEC = 60; var percent = 0; var startTime = new Date().getTime(); setTimeout(updateProgress, 1000 / FRAMES_PER_SEC); function updateProgress() { var currentTime = new Date().getTime(); // 1000 to convert to milliseconds, and 100 to convert to percentage percent = (currentTime - startTime) / (TOTAL_SEC * 1000) * 100; $("#progressbar").progressbar({ value: percent }); if (percent >= 100) { window.location = "newLocation.html"; } else { setTimeout(updateProgress, 1000 / FRAMES_PER_SEC); } } 

Questo è un timer che ho fatto per un mio progetto musicale che fa questa cosa. Timer accurato su tutti i dispositivi.

 var Timer = function(){ var framebuffer = 0, var msSinceInitialized = 0, var timer = this; var timeAtLastInterval = new Date().getTime(); setInterval(function(){ var frametime = new Date().getTime(); var timeElapsed = frametime - timeAtLastInterval; msSinceInitialized += timeElapsed; timeAtLastInterval = frametime; },1); this.setInterval = function(callback,timeout,arguments) { var timeStarted = msSinceInitialized; var interval = setInterval(function(){ var totaltimepassed = msSinceInitialized - timeStarted; if (totaltimepassed >= timeout) { callback(arguments); timeStarted = msSinceInitialized; } },1); return interval; } } var timer = new Timer(); timer.setInterval(function(){console.log("This timer will not drift."),1000} 

Odio dirlo, ma non penso che ci sia un modo per alleviare questo. Penso che dipenda dal sistema client, però, quindi un motore o una macchina javascript più veloce potrebbe renderlo leggermente più accurato.

Per la mia esperienza è stato perso lo sforzo, anche se il più piccolo intervallo di tempo in cui io abbia mai avuto modo di intervenire è di circa 32-33 ms. …

C’è sicuramente una limitazione qui. Per darti un po ‘di prospettiva, il browser Chrome appena rilasciato è abbastanza veloce da poter eseguire setTimeout (function () {}, 0) in 15-20 ms mentre i vecchi motori Javascript impiegavano centinaia di millisecondi per eseguire quella funzione. Sebbene setTimeout usi millisecondi, nessuna macchina virtuale javascript in questo momento può eseguire codice con quella precisione.

Dan, dalla mia esperienza (che include l’implementazione del linguaggio SMIL2.1 in JavaScript, dove è in uso la gestione del tempo), posso assicurarti che in realtà non hai mai bisogno di alta precisione di setTimeout o setInterval.

Ciò che importa comunque è l’ordine in cui setTimeout / setInterval viene eseguito in coda – e che funziona sempre perfettamente.

I timeout JavaScript hanno un limite di fatto di 10-15 ms (non sono sicuro di quello che stai facendo per ottenere 200ms, a meno che tu non stia facendo 185ms di effettiva esecuzione js). Ciò è dovuto al fatto che Windows ha una risoluzione del timer standard di 15 ms, l’unico modo per fare meglio è usare i timer a risoluzione più alta di Windows che è un’impostazione estesa al sistema in modo da poter avvitare altre applicazioni sul sistema e anche masticare la durata della batteria (Chrome ha un bug di Intel su questo problema).

Lo standard defacto di 10-15 ms è dovuto a persone che utilizzano timeout di 0 ms sui siti Web, ma poi codificano in un modo che presuppone che si presume un timeout di 10-15 ms (ad esempio giochi js che presuppongono 60fps ma richiedono 0ms / frame senza logica delta il gioco / il sito / l’animazione raggiunge alcuni ordini di grandezza più velocemente del previsto). Per tener conto di ciò, anche su piattaforms che non hanno problemi con il timer di Windows, i browser limitano la risoluzione del timer a 10 ms.

Ecco cosa uso. Poiché è JavaScript, posterò sia le mie soluzioni Frontend che node.js:

Per entrambi, utilizzo la stessa funzione di arrotondamento decimale che consiglio vivamente di tenere a braccia spalancate perché ragioni:

 const round = (places, number) => +(Math.round(number + `e+${places}`) + `e-${places}`) 

places – Numero di posizioni decimali alle quali arrotondare, questo dovrebbe essere sicuro e dovrebbe evitare qualsiasi problema con i float (alcuni numeri come 1.0000000000005 ~ possono essere problematici). Ho passato il tempo a cercare il modo migliore per arrotondare i decimali forniti dai timer ad alta risoluzione convertiti in millisecondi.

that + symbol – È un operatore unario che converte un operando in un numero, praticamente identico a Number()

Browser

 const start = performance.now() // I wonder how long this comment takes to parse const end = performance.now() const result = (end - start) + ' ms' const adjusted = round(2, result) // see above rounding function 

node.js

 // Start timer const startTimer = () => process.hrtime() // End timer const endTimer = (time) => { const diff = process.hrtime(time) const NS_PER_SEC = 1e9 const result = (diff[0] * NS_PER_SEC + diff[1]) const elapsed = Math.round((result * 0.0000010)) return elapsed } // This end timer converts the number from nanoseconds into milliseconds; // you can find the nanosecond version if you need some seriously high-resolution timers. const start = startTimer() // I wonder how long this comment takes to parse const end = endTimer(start) console.log(end + ' ms') 

Potresti prendere in considerazione l’ utilizzo dell’orologio webaudio html5 che utilizza l’ora del sistema per una migliore precisione