Ingeniería del Enfoque Profundo: Temporizadores Anti-Deriva en JavaScript (performance.now vs setInterval)

Por qué los temporizadores basados únicamente en setInterval(fn, 1000) pierden minutos enteros cuando la pestaña pasa a segundo plano y cómo diseñar un reloj exacto basado en deltas monotónicos.

La técnica Pomodoro —desarrollada por Francesco Cirillo a finales de los años 80— estructura el trabajo intelectual en bloques ininterrumpidos de 25 minutos de enfoque profundo (Deep Work) seguidos de 5 minutos de descanso cognitivo. Parece trivial programar una cuenta regresiva de 25 minutos en JavaScript, pero cualquiera que haya utilizado un temporizador web básico habrá notado un fallo frustrante: cambias de pestaña para programar o leer un PDF durante 20 minutos, y al regresar a la pestaña del temporizador descubres que apenas han transcurrido 4 minutos en el reloj.

1. ¿Por qué falla setInterval(fn, 1000) en los Navegadores Modernos?

Muchos tutoriales de programación enseñan a construir un reloj restando un segundo a una variable en cada ejecución de setInterval:

// ❌ ANTI-PATRÓN: Temporizador con deriva severa en segundo plano
let remainingSeconds = 25 * 60;
setInterval(() => {
  remainingSeconds--;
  updateUI(remainingSeconds);
}, 1000);

Este código falla en el mundo real por dos razones fundamentales del motor de ejecución del navegador:

  • El Event Loop de un solo hilo: setInterval(fn, 1000) no garantiza que la función se ejecute exactamente cada 1,000.00 ms; solo garantiza que la tarea se encolará en la cola de macrotareas como mínimo tras 1,000 ms. Si el hilo principal está ocupado renderizando estilos, cada tick acumula entre 2 y 15 ms de retraso.
  • Background Tab Throttling (Ahorro de Energía del Navegador): Para ahorrar batería y CPU, Chrome, Edge, Firefox y Safari estrangulan (throttle) los temporizadores de las pestañas en segundo plano a 1 ejecución por segundo inicialmente y, tras 5 minutos de ocultamiento, llegan a espaciar las ejecuciones a una sola vez por minuto (Intensive Wake Up Throttling).

2. La Solución de Ingeniería: Reloj Basado en Marcas de Tiempo Absolutas (Delta Timing)

Para que un temporizador como PomoTask sea matemáticamente exacto al milisegundo sin importar si la pestaña está minimizada, si la computadora entra en suspensión breve o si el navegador congela los hilos en segundo plano, nunca debemos contar cuántos ticks han ocurrido. En su lugar, almacenamos el instante exacto del futuro (targetEndTime) en el que debe finalizar el ciclo:

// ✅ PATRÓN CORRECTO: Temporizador Anti-Deriva basado en tiempo real
class PrecisionPomodoroTimer {
  constructor(durationSeconds, onTick, onComplete) {
    this.durationMs = durationSeconds * 1000;
    this.remainingMs = this.durationMs;
    this.targetEndTime = null;
    this.timerId = null;
    this.onTick = onTick;
    this.onComplete = onComplete;
  }

  start() {
    if (this.timerId) return;
    // Anclar el tiempo objetivo respecto al reloj del sistema
    this.targetEndTime = Date.now() + this.remainingMs;

    const tick = () => {
      const now = Date.now();
      this.remainingMs = Math.max(0, this.targetEndTime - now);
      this.onTick(Math.ceil(this.remainingMs / 1000));

      if (this.remainingMs <= 0) {
        this.stop();
        this.onComplete();
      }
    };

    tick();
    this.timerId = setInterval(tick, 250); // Muestreo a 4 Hz para máxima fluidez visual
  }

  pause() {
    if (!this.timerId) return;
    clearInterval(this.timerId);
    this.timerId = null;
    this.remainingMs = Math.max(0, this.targetEndTime - Date.now());
  }
}

3. Persistencia de Estado ante Recargas Accidentales

Además del cálculo diferencial con Date.now(), guardar el valor de targetEndTime y el estado actual (running | paused) en localStorage permite que, si el usuario cierra accidentalmente la pestaña en el minuto 14 de su Pomodoro y vuelve a abrir la aplicación 5 minutos después, el temporizador recupere instantáneamente la cuenta exacta en el minuto 9 sin perder un solo segundo de su sesión de estudio.

IX

Escrito por Equipo de Ingeniería Inovaxsoft

El equipo de ingeniería de Inovaxsoft investiga, desarrolla y documenta arquitecturas web de baja latencia, sistemas distribuidos sobre Kubernetes ARM64 y herramientas de productividad como PomoTask y Roomly.