timeout-flow
v0.0.17
Published
Fluent, composable, pauseable JavaScript timers and time control flows — plus RAF utilities for frame-based logic.
Maintainers
Readme
TimeoutFlow
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
Philosophy
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.
In Other Words:
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.
Installation
npm install timeout-flowFeatures
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()
Usage Examples
// 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();Utilities
import { debounce, throttle, retry, waitFor } from 'timeout-flow';debounce('300ms', fn)— Run only after silencethrottle('1s', fn)— Run at most once per time windowretry(fn, { attempts, delay, backoff })— Resilient retry for async callswaitFor(() => condition, { timeout, interval })— Await condition change
Frame-Based Timing (RAF Utilities)
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.
API Summary
| 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 |
Key Advantages
- 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
🛠 Example Usage
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 Locations
| 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 |
License
--{DR.WATT v3.0}--
