Seleccionar página

Limitaciones del uso de Date.now()

Cuando uno empieza a realizar los primeros análisis de rendimiento de su código en Javascript es muy común utilizar un par de variables, una antes y otras después del código que queremos comprobar, donde almacenamos la fecha y hora actual obtenida con Date.now() y luego comparamos ambas. No es una técnica incorrecta, pero en muchas ocasiones nuestro código se va a ejecutar con mayor velocidad del que somos capaces de capturar con Date.now(), es decir, obtenemos el mismo valor en las dos variables y por lo tanto el tiempo de ejecución que nos aparece es 0.

var start = new Date ();
var a     = 1000;
var end   = new Date ();
console.log (end - start);

Lo que sucede es que nuestro código a tardado menos de un milisegundo en ejecutarse y, por lo tanto, las dos variables tienen la misma fecha y hora. Date.now() devuelve el número de milisegundos pasados desde el 1 de enero de 1970 a las 0:00 hora UTC. Este método no dispone de mayor precisión que el milisegundo, yaunque nos parezca un periodo de tiempo muy pequeño, los motores modernos de Javascript pueden hacer muchas cosas en 1 milisegundo.

También tenemos que saber que la mayoría de las veces podemos confiar en la comparación de valores de vueltos por new Date(), aunque puedan tener una precisión limitada, pero en algunas ocasiones podemos obtener valores incorrectos por la actualización de la hora del sistema. Hay que tener en cuenta que el reloj del ordenador puede ser actualizado por el usuario o por algún servicio de sincronización de la hora del sistema como NTP y si comparamos dos valores tomados antes y después de esta actualización del reloj podemos, incluso, obtener valores negativos.

Tiempo de alta precisión, relativo y monótono

En 2012 -hace ya bastante tiempo- apareció en los navegadores el API performance.now() para obtener diferencias de tiempos con una precisión elevada (High Resolution Time). Este API está implementada en IE10, Firefox 15, Chrome 24, Safari 8 y Node 8.5 (en este último con require('perf_hooks').performance, que está publicado en forma experimental), por lo que podemos utilizarla sin temor a que no esté disponible en nuestro sistema.

var start = performance.now();
var a     = 1000;
var end   = performance.now();
console.log (end - start);

Las llamadas a performance.now() devuelven un valor de milisegundos, con decimales, con una precisión de milésima de milisegundo (millonesima parte de un segundo, o nanosegundo), por lo que su precisión es muy alta. Dan el tiempo desde el inicio de nuestro programa, no son una fecha y hora como tal, y por lo tanto sólo son útiles si los utilizados como marcas de tiempo, por ejemplo para analizar el rendimiento o hacer algún tipo de marca temporal de alta precisión.

La otra ventaja es que los valores de tiempo devueltos al llamar al método performance.now() son siempre monótonamente crecientes y no están sujetos a los ajustes, sesgo o desviación del reloj del sistema. La diferencia entre cualquiera de los dos valores obtenidos de performance.now() nunca puede ser negativa y crece de forma estable (monótona).

En Node, además de poder utilizar la función por medio del paquete perf_hooks, que en estos momentos está publicado en modo experimental, tenemos a nuestra disposición dos métodos muy sencillos para obtener un tiempo de alta precisión: process.hrtime() que devuelve una matriz con dos valores, uno con el valor en milisegundos y otro los nanosegundos y también el más moderno process.hrtime.bigint() que devuelve directamente un valor de tipo bigint con los nanosegundos.

Si queremos disponer de una función que funcione directamente tanto en navegador como en servidor, podemos construirla con mucha facilidad de esta manera:

/**
 * get high precision relative time
 * @returns {number}
 */
const hrtime =
  (typeof window !== 'undefined' &&
  typeof window.performance !== 'undefined' &&
  window.performance.now) ?
    window.performance.now.bind (window.performance) :
    (typeof require !== 'undefined') ?
      require ('perf_hooks').performance.now :
      function () {
        var h = process.hrtime ();
        return (h[ 0 ] + (h[ 1 ] / 1e9)) * 1000;
      };

var start = hrtime ();
for (var n = 0; n < 10000; n++) {
  var x = Math.log(n);
}
var end   = hrtime ();
console.log (end - start);

Fecha y hora de alta precisión

Aunque no suele ser necesario, si queremos obtener una fecha y hora de alta precision podemos partir de performance.now() y apoyarnos en performance.timeOrigin que tiene el valor inicial a partir del cual está calcuándose el valor devuelto por performance.now().

var start = performance.now() + performance.timeOrigin;
var a     = 1000;
var end   = performance.now() + performance.timeOrigin;
console.log ('start:', start, 'end:', end, 'Date.now()');

El problema es que performance.timeOrigin es relativamente reciente y es posible que no lo encontremos en todos los navegadores y tendríamos, en esos casos, que utilizar otras propiedades ahora deprecadas como performance.timing.navigationStart que, con un poco menos de precisión, son equivalentes.

Seguridad y tiempo de alta precisión

A fin de evitar algunas confusiones, queremos aclarar que el acceso a información precisa de tiempo es una necesidad común para muchas aplicaciones y no estamos haciendo nada incorrecto al usar estas API. Por ejemplo, son imprescindibles para la coordinación de elementos en animaciones o para realizar un análisis del rendimiento de las partes críticas del código.

El problema es que el acceso a esta información de tiempo alta precisión también puede ser utilizada con fines maliciosos por un atacante para obtener o inferir datos, de ahí que se haya producido un cierto debate sobre estas API y su nivel de precisión. Algunos de estos problemas de privacidad y seguridad consisten en que la alta precisión de estas API podrían permitir a un sitio web malintencionado diferenciar entre subconjuntos de usuarios y, en algunos casos extremos, identificar a un usuario en particular.

Algunos navegadores (especialmente Firefox) han incorporado mecanismos para reducir la precisión de estas API y evitar estas actividades de rastreo, pero no se han generalizado. En cualquier caso, a pesar de que puedan ser utilizadas para realizar algún tipo de rastreo, estas son una APIs perfectamente válidas y utilizables sin ningún tipo de prevención.

Novedades

¿Qué pasa con import y los web components?

¿Qué pasa con import y los web components?

Uno de los más controvertidos pilares de los componentes web ha sido el HTML Import. Considerado en estos momentos como una funcionalidad discontinuada, debemos conocer como sacar el máximo partido la instrucción import de Javascipt para gestionar la carga de nuestros componentes.
Template a fondo

Template a fondo

Hay dos formas estándar de crear contenido en un componente de forma flexible: la etiqueta template, que se considera como uno de los pilares de los Web Components y las template string de Javascript, que son una buena alternativa para generar el Shadow DOM con interpolación de datos.
Light DOM a fondo

Light DOM a fondo

El Light DOM es un espacio compartido entre nuestro componente web y el DOM general, que podemos utilizar para insertar contenido o configurar nuestro componente. Es una muy interesante característica que debemos conocer.
Shadow DOM a fondo

Shadow DOM a fondo

Para que los componentes web no colisionen unos con otros es muy útil utilizar el Shadow DOM para aislar el DOM y el CSS de cada componente. Esta característica se puede aplicar también a elementos HTML sin necesidad de utilizar Custom Elements, pero es con estos donde cobra todo su potencial. Demos un repaso profundo a las capacidades del Shadow DOM.