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.
// 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;
// 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
pause(), the first fire after resume() comes 400ms later — then every period after that is the full delay again.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.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().