¿Cuál es la razón por la que JavaScript setTimeout es tan inexacto?
Tengo este código aquí:
var date = new Date();
setTimeout(function(e) {
var currentDate = new Date();
if(currentDate - date >= 1000) {
console.log(currentDate, date);
console.log(currentDate-date);
}
else {
console.log("It was less than a second!");
console.log(currentDate-date);
}
}, 1000);
En mi computadora siempre se ejecuta correctamente, con 1000 en la salida de la consola. Curiosamente, en otra computadora, el mismo código, la devolución de llamada del tiempo de espera comienza en menos de un segundo y la diferencia currentDate - date
está entre 980 y 998.
Conozco la existencia de bibliotecas que solucionan esta inexactitud (por ejemplo, Tock ).
Básicamente, mi pregunta es: ¿ Cuáles son las razones por las que setTimeout
no se dispara en el retraso indicado? ¿Podría ser que la computadora es demasiado lenta y el navegador intenta automáticamente adaptarse a la lentitud y activa el evento antes?
PD: Aquí hay una captura de pantalla del código y los resultados ejecutados en la consola JavaScript de Chrome:
No se supone que sea particularmente preciso. Hay una serie de factores que limitan la rapidez con la que el navegador puede ejecutar el código; citando de MDN :
Además de "sujetar", el tiempo de espera también puede activarse más tarde cuando la página (o el sistema operativo/navegador) esté ocupado con otras tareas.
En otras palabras, la forma en que setTimeout
generalmente se implementa es solo para ejecutarse después de un retraso determinado y una vez que el hilo del navegador esté libre para ejecutarlo.
Sin embargo, diferentes navegadores pueden implementarlo de diferentes maneras. Aquí hay algunas pruebas que hice:
var date = new Date();
setTimeout(function(e) {
var currentDate = new Date();
console.log(currentDate-date);
}, 1000);
// Browser Test1 Test2 Test3 Test4
// Chrome 998 1014 998 998
// Firefox 1000 1001 1047 1000
// IE 11 1006 1013 1007 1005
Tal vez el <1000 veces de Chrome podría atribuirse a una inexactitud en el Date
tipo, o tal vez podría ser que Chrome use una estrategia diferente para decidir cuándo ejecutar el código; tal vez esté tratando de encajarlo en el intervalo de tiempo más cercano, incluso si el tiempo de espera aún no se ha completado.
En resumen, no debería usarlo setTimeout
si espera una sincronización confiable y consistente en una escala de milisegundos.
En general, los programas informáticos son muy poco fiables cuando intentan ejecutar cosas con una precisión superior a 50 ms. La razón de esto es que incluso en un procesador hyperthreaded octacore el sistema operativo generalmente hace malabarismos con varios cientos de procesos y subprocesos, a veces miles o más. El sistema operativo hace que toda esa multitarea funcione programándolas todas para obtener una porción de tiempo de CPU, una tras otra, lo que significa que tienen "unos pocos milisegundos de tiempo como máximo para hacer lo suyo".
Implícitamente, esto significa que si establece un tiempo de espera de 1000 ms, las posibilidades no son pequeñas de que el proceso actual del navegador ni siquiera se esté ejecutando en ese momento, por lo que es perfectamente normal que el navegador no se dé cuenta hasta 1005, 1010 o incluso 1050 milisegundos que debería estar ejecutando la devolución de llamada dada.
Generalmente esto no es un problema, sucede y rara vez es de suma importancia. Si es así, todos los sistemas operativos proporcionan temporizadores a nivel de kernel que son mucho más precisos que 1 ms y permiten al desarrollador ejecutar código precisamente en el momento correcto. Sin embargo, JavaScript, como entorno fuertemente aislado, no tiene acceso a objetos del kernel como ese, y los navegadores se abstienen de usarlos ya que, en teoría, podría permitir que alguien ataque la estabilidad del sistema operativo desde el interior de una página web, construyendo cuidadosamente código que priva a otros. hilos inundándolos con muchos temporizadores peligrosos.
En cuanto a por qué la prueba arroja 980, no estoy seguro; eso dependerá exactamente de qué navegador esté utilizando y qué motor de JavaScript. Sin embargo, puedo entender completamente si el navegador corrige manualmente un poco hacia abajo la carga y/o la velocidad del sistema, asegurándose de que "en promedio, el retraso sigue siendo aproximadamente el tiempo correcto": tendría mucho sentido desde el principio de sandboxing simplemente aproximar la cantidad de tiempo requerido sin sobrecargar potencialmente el resto del sistema.
Alguien por favor corríjame si estoy malinterpretando esta información:
Según una publicación de John Resig sobre la inexactitud de las pruebas de rendimiento en todas las plataformas (el énfasis es mío)
Dado que los tiempos del sistema se redondean constantemente hacia abajo hasta el último tiempo consultado ( cada uno con una diferencia de aproximadamente 15 ms ), la calidad de los resultados de rendimiento se ve seriamente comprometida.
Por lo tanto, hay un error de hasta 15 ms en cada extremo en comparación con la hora del sistema.