Fluent, human-readable time control for JavaScript.
TimeoutFlow makes working with time-based logic intuitive — think of it as a modern, composable upgrade to setTimeout
and setInterval
, with added powers like chaining, conditional logic, pause/resume, retries, and more.
- Minified: 6.64 KB
- Gzipped: 2.6 KB
TimeoutFlow is not just a wrapper for setTimeout
.
It's a composable mini-framework for expressing time as fluent logic.
We believe temporal behavior in JavaScript should be:
- Readable – durations like "1s" and "500ms" are easier to reason about than magic numbers.
- Composable – sequencing events should be declarative, not a tangle of nested callbacks or timers.
- Controllable – any timer should be pauseable, resumable, and cancelable at any moment.
-
Branchable – real flows require
if
,while
,label
, andjumpTo()
— not just repetition. - Tiny – no dependencies, no bloat, and no reactivity engine required.
TimeoutFlow gives you atomic time primitives (after
, every
, debounce
, retry
)
and a fluent builder (flow()
) to script rich behavior over time — like a timeline you can control.
Think of TimeoutFlow as setTimeout() with superpowers. But more importantly, think of it as a way to write time like you write logic.
flow()
.after('1s', () => console.log('Start'))
.every('500ms', (i) => console.log(`Tick ${i}`), 3)
.after('1s', () => console.log('Done'))
.start();
This isn’t about wrapping timers. It’s about orchestrating intent — clearly, fluently, and with full control.
npm install timeout-flow
-
after("1s", fn)
— delay execution (viaAfterTimer
) -
every("500ms", fn, count?)
— repeat execution with optional limit (viaEveryTimer
) -
flow()
— create fluent, chainable timelines with:-
.after()
,.every()
,.loop(n)
-
.if()
,.unless()
,.label()
,.jumpTo()
-
.while()
,.doWhile()
-
-
Utilities:
debounce()
,throttle()
,retry()
,waitFor()
// 1. Delayed Execution
import { after } from 'timeout-flow';
after('2s', () => console.log('Waited 2 seconds...'));
// 2. Repeating with Pause & Resume
import { every, after as wait } from 'timeout-flow';
const ticker = every('1s', i => console.log(`Tick ${i}`), 5);
wait('2.5s', () => ticker.pause());
wait('4s', () => ticker.resume());
// 3. Debounced Input
import { debounce } from 'timeout-flow';
const search = debounce('300ms', (e) => {
console.log('Searching for:', e.target.value);
});
document.querySelector('input').addEventListener('input', search);
// 4. Retry a Failing Request
import { retry } from 'timeout-flow';
await retry(() => fetch('/api/data'), {
attempts: 4,
delay: '1s',
backoff: true
});
// 5. Wait for DOM Change
import { waitFor } from 'timeout-flow';
await waitFor(() => document.querySelector('#loaded'), {
interval: '250ms',
timeout: '5s'
});
console.log('Element loaded!');
// 6. Fluent Timeline
import { flow } from 'timeout-flow';
flow()
.after('1s', () => console.log('Step 1'))
.every('500ms', (i) => console.log(`Tick ${i}`), 3)
.after('1s', () => console.log('Final Step'))
.start();
// 7. Conditional & Labeled Logic
let debug = true;
flow()
.after('1s', () => console.log('Boot sequence'))
.if(() => debug)
.after('500ms', () => console.log('Debug logs enabled'))
.label('loop')
.every('1s', i => console.log(`Frame ${i}`), 3)
.after('500ms', () => console.log('Restarting...'))
.jumpTo('loop')
.start();
// 8. Controlled Loop
let energy = 3;
flow()
.doWhile(() => energy-- > 0)
.every('400ms', () => console.log(`Blast (${energy})`))
.after('1s', () => console.log('Energy depleted'))
.start();
import { debounce, throttle, retry, waitFor } from 'timeout-flow';
-
debounce('300ms', fn)
— Run only after silence -
throttle('1s', fn)
— Run at most once per time window -
retry(fn, { attempts, delay, backoff })
— Resilient retry for async calls -
waitFor(() => condition, { timeout, interval })
— Await condition change
These helpers use requestAnimationFrame
under the hood to provide smooth, energy-efficient timing. Ideal for visual UI flows, canvas apps, scroll/resize behavior, and performance-sensitive interactions.
All raf
utilities automatically pause in background tabs, unlike timers.
Function | Purpose | Best For |
---|---|---|
afterRaf() |
Runs a function once after N ms, using requestAnimationFrame . |
Idle effects, UI post-load, paint batching |
everyRaf() |
Repeats a function every N ms, throttled via frames. | Sanity loops, smooth polling, visual checks |
debounceRaf() |
Debounces a function using frames instead of timeouts. | Drag/move handlers, visual updates |
debounceRaf('300ms', fn) |
Debounces like traditional debounce, but frame-aware. | Resize events, paused background flows |
throttleRaf() |
Throttles execution to at most once per frame. | Scroll events, pointermove, paint-heavy flows |
throttleRaf(fn, 2) |
Throttles to once every 3 frames (frameSkip = 2 ). |
Advanced visuals, slower sync without timers |
waitForRaf() |
Waits for a condition to become true using a frame-based loop. | DOM readiness, layout stability, visibility checks |
- Frame-sync: Triggered in sync with visual updates (60Hz or higher)
- Background-tab safe: No CPU use when inactive
- Energy efficient: Great for battery-conscious apps
- Smoother UX: Especially under load or heavy visuals
import { debounceRaf } from 'timeout-flow';
const onMouseMove = debounceRaf(() => {
drawPreview();
});
const onResize = debounceRaf('250ms', () => {
updateLayout();
});
import { afterRaf } from 'timeout-flow';
afterRaf('2s', () => {
showIntroAnimation();
});
import { everyRaf } from 'timeout-flow';
const loop = everyRaf('1s', () => {
console.log('heartbeat');
});
import { throttleRaf } from 'timeout-flow';
const onScroll = throttleRaf((e) => {
handleScroll(e);
});
const onDrag = throttleRaf(drawFrame, 2);
import { waitForRaf } from 'timeout-flow';
await waitForRaf(() => document.querySelector('#panel')?.offsetHeight > 0);
File | Description |
---|---|
raf/afterRaf.js |
One-time timer with frame pause support |
raf/everyRaf.js |
Interval timer using requestAnimationFrame
|
raf/debounceRaf.js |
Smart debounce with optional duration and frame pause |
raf/throttleRaf.js |
Input-event throttle using frame-skip control |
raf/waitForRaf.js |
Waits for truthy condition using passive frame polling |
--{DR.WATT v3.0}--