Resumable IntervalLoading saved progress…

Resumable Interval

Implement resumableInterval(callback, delay) — a repeating timer you can pause and resume without losing the progress already made toward the next tick. A plain setInterval fires callback every delay milliseconds but has no pause. Here, pausing freezes the countdown mid-period and remembers how far it got; resuming waits only the leftover time, fires once, and then carries on at the full delay. Think of a kitchen timer you can stop with two minutes left and restart later from two minutes — not from the top.

Signature

// callback: () => void   — invoked on every tick.
// delay:    number       — milliseconds between ticks.
// returns a controller:
//   {
//     start():   void   — begin firing every `delay` ms.
//     pause():   void   — freeze the countdown, remember the elapsed part.
//     resume():  void   — wait only the remaining part, fire, then full delays.
//     stop():    void   — cancel everything; a later start() begins fresh.
//   }
function resumableInterval(callback, delay): Controller;

Examples

// delay = 1000. Pause partway through the first period, idle, then resume.
const c = resumableInterval(onTick, 1000);
c.start();
// ...600ms pass...   (600 of the 1000ms period have elapsed)
c.pause();
// ...5000ms pass...  (paused — onTick does NOT fire, no progress is lost)
c.resume();
// ...400ms pass...   → onTick fires once (only the leftover 400ms was waited)
// ...1000ms pass...  → onTick fires again (full period resumes)
// stop() cancels everything; a later start() does not remember the old progress.
const c = resumableInterval(onTick, 1000);
c.start();
// ...600ms pass...
c.stop();        // cancelled — no pending fire
c.start();       // fresh: the next fire is a full 1000ms away, not 400ms

Notes

  • Resume waits the remainder, not a full delay. If 600ms of a 1000ms period elapsed before pause(), the first fire after resume() comes 400ms later — then every period after that is the full delay again.
  • Pause must not fire. While paused, no amount of elapsed wall-clock time produces a tick. The banked remainder stays fixed until you resume.
  • Calls from the wrong state are no-ops. resume() when not paused, pause() when not running, and a second start() while already running each do nothing — no throw, no extra timer.
  • stop() resets to fresh. After stop(), there is no remembered remainder; the next start() begins a whole new period from zero.
  • Use setTimeout chaining, not setInterval. A single setInterval can't wait a partial first period, which is exactly what resume() needs. Measure elapsed time with Date.now().
Loading editor…