npm package discovery and stats viewer.

Discover Tips

  • General search

    [free text search, go nuts!]

  • Package details

    pkg:[package-name]

  • User packages

    @[username]

Sponsor

Optimize Toolset

I’ve always been into building performant and accessible sites, but lately I’ve been taking it extremely seriously. So much so that I’ve been building a tool to help me optimize and monitor the sites that I build to make sure that I’m making an attempt to offer the best experience to those who visit them. If you’re into performant, accessible and SEO friendly sites, you might like it too! You can check it out at Optimize Toolset.

About

Hi, 👋, I’m Ryan Hefner  and I built this site for me, and you! The goal of this site was to provide an easy way for me to check the stats on my npm packages, both for prioritizing issues and updates, and to give me a little kick in the pants to keep up on stuff.

As I was building it, I realized that I was actually using the tool to build the tool, and figured I might as well put this out there and hopefully others will find it to be a fast and useful way to search and browse npm packages as I have.

If you’re interested in other things I’m working on, follow me on Twitter or check out the open source projects I’ve been publishing on GitHub.

I am also working on a Twitter bot for this site to tweet the most popular, newest, random packages from npm. Please follow that account now and it will start sending out packages soon–ish.

Open Software & Tools

This site wouldn’t be possible without the immense generosity and tireless efforts from the people who make contributions to the world and share their work via open source initiatives. Thank you 🙏

© 2026 – Pkg Stats / Ryan Hefner

timeout-flow

v0.0.19

Published

Fluent, composable, pauseable JavaScript timers and time control flows — plus RAF utilities for frame-based logic.

Readme

timeout-flow

npm downloads bundle size license stars

Fluent, human-readable time control for JavaScript. A modern, composable upgrade to setTimeout and setInterval with chaining, conditional logic, pause/resume control, retries, and RAF utilities.

Features

  • Readable durations: "1s", "500ms", plain numbers
  • Pause / resume / cancel controllers for after(), every(), RAF variants
  • flow() builds declarative timelines with branching and labels
  • retry() with backoff, jitter, and AbortSignal cancellation
  • waitFor() polls a predicate with timeout
  • RAF variants (afterRaf, everyRaf, debounceRaf, throttleRaf, waitForRaf) for frame-aligned timing
  • ESM-first, tree-shakeable. Per-file subpath exports for size-sensitive consumers (~1.2 KB gzipped for a single primitive)
  • Zero dependencies
  • ~6–7 KB minified for the full kit (~2–3 KB gzipped)

Install

pnpm add timeout-flow

Quick start

import { after } from 'timeout-flow';

function greet() {
  console.log('Hello');
}

const ctrl = after('2s', greet);

ctrl.pause();
ctrl.resume();
ctrl.cancel();

API

after(duration, fn, options?)

Run once after a delay.

import { after } from 'timeout-flow';

after('2s', function greet() {
  console.log('Hello');
});

every(duration, fn, options?)

Repeat execution with optional limit.

import { every } from 'timeout-flow';

const ticker = every('1s', function tick(i) {
  console.log('Tick', i);
}, { max: 5 });

debounce(duration, fn, options?)

Delay execution until inactivity.

import { debounce } from 'timeout-flow';

const debouncedSearch = debounce('300ms', function search(event) {
  console.log('Searching for:', event.target.value);
});

debouncedSearch.cancel();
debouncedSearch.flush();

throttle(duration, fn, options?)

Limit execution frequency.

import { throttle } from 'timeout-flow';

const onScroll = throttle('250ms', function handleScroll() {
  console.log('scroll');
});

onScroll.cancel();
onScroll.flush();

retry(fn, options?)

Retry async operations with backoff and jitter.

import { retry } from 'timeout-flow';

await retry(async function fetchData() {
  return fetch('/api/data');
}, {
  attempts: 5,
  delay: '500ms',
  backoff: true,
  factor: 2,
  maxDelay: '5s',
  jitter: 'decorrelated'
});

waitFor(predicate, options?)

Wait until a condition becomes true.

import { waitFor } from 'timeout-flow';

await waitFor(function checkLoaded() {
  return document.querySelector('#loaded');
}, {
  interval: '250ms',
  timeout: '5s',
  immediate: true
});

flow()

Build declarative time flows.

import { flow } from 'timeout-flow';

flow()
  .after('1s', function stepOne() {
    console.log('Step 1');
  })
  .every('500ms', function tick(i) {
    console.log('Tick', i);
  }, { max: 3 })
  .after('1s', function finalStep() {
    console.log('Final Step');
  })
  .start();

Flow reads like a timeline and keeps duration-first ordering for readability.

RAF utilities

Frame-based timing powered by requestAnimationFrame. Ideal for visual updates, scroll handlers, layout checks, and animation loops.

Available: afterRaf(), everyRaf(), debounceRaf(), throttleRaf(), waitForRaf().

import { throttleRaf } from 'timeout-flow';

const onScroll = throttleRaf(function drawFrame() {
  console.log('draw');
});

onScroll.cancel();
onScroll.flush();

Controllers

Most time-based primitives return a controller object:

const ctrl = after('1s', () => console.log('done'));

ctrl.pause();
ctrl.resume();
ctrl.cancel();
ctrl.reset?.();

console.log(ctrl.isRunning);
console.log(ctrl.isPaused);
console.log(ctrl.isFinished);

Shared by after(), every(), afterRaf(), everyRaf(). All controllers are pause-safe (paused time does not count), cancel-safe, monotonic-time based, and AbortSignal-aware where supported.

AbortSignal support

Many utilities accept { signal } for automatic cancellation.

import { after } from 'timeout-flow';

const ac = new AbortController();

after('2s', function doWork() {
  console.log('Will not run if aborted');
}, { signal: ac.signal });

ac.abort();

If already aborted at creation time, no work is scheduled.

Public surface

import {
  after,
  every,
  debounce,
  throttle,
  retry,
  waitFor,
  flow,

  afterRaf,
  everyRaf,
  debounceRaf,
  throttleRaf,
  waitForRaf
} from 'timeout-flow';

Notes

Tree-shaking and per-primitive imports

Two import styles, same package, choose by bundle-size sensitivity:

// Full kit. Every primitive available on one import.
// Modern bundlers (esbuild, vite, rollup, webpack 5) will tree-shake
// unused exports because the package ships `sideEffects: false` and the
// `.` entrypoint points at ESM source instead of a pre-minified blob.
import { after, debounce, retry } from 'timeout-flow';
// Per-primitive subpath. Explicit minimum surface, useful when you know
// you only need one primitive and want the bundle to reflect that
// without relying on the bundler's tree-shaker. Same source either way.
import { after }     from 'timeout-flow/after';
import { every }     from 'timeout-flow/every';
import { debounce }  from 'timeout-flow/debounce';
import { throttle }  from 'timeout-flow/throttle';
import { retry }     from 'timeout-flow/retry';
import { waitFor }   from 'timeout-flow/wait-for';
import { flow }      from 'timeout-flow/flow';
import { afterRaf, everyRaf, debounceRaf, throttleRaf, waitForRaf } from 'timeout-flow/raf';
import { parseDuration } from 'timeout-flow/parse-duration';

Measured for a single after() call in a typical browser bundler (esbuild, minified + gzip):

| Import style | Gzip | |---|---:| | pre-0.0.19 default (full minified bundle) | 5098 bytes | | 0.0.19 default (import { after } from 'timeout-flow') | 1199 bytes | | 0.0.19 subpath (import { after } from 'timeout-flow/after') | 1199 bytes |

For unbundled <script type="module"> consumption of the full kit, use the timeout-flow/min subpath, which points at the pre-built minified IIFE.

Clean signatures

timeout-flow supports both natural-language and function-first argument ordering:

after('1s', done);
// or
after(done, '1s');

This applies to after(), every(), debounce(), throttle(). Use whichever reads best in your codebase.

Philosophy

timeout-flow is not just a wrapper around timers, it's a composable toolkit for expressing time as readable logic. Temporal behavior should be:

  • Readable. Durations like "1s" and "500ms" beat magic numbers.
  • Composable. Sequencing should be declarative.
  • Controllable. Timers should pause, resume, cancel.
  • Branchable. Real flows need if, while, label, jumpTo().
  • Tiny. No runtime bloat.

Think of timeout-flow as setTimeout() with superpowers.


License

Licensed under AGPL-3.0 with WATT3D Additional Terms. See LICENSE and ADDITIONAL_TERMS.md. Commercial AI/model-training use requires compliance with those terms or a separate WATT3D license. © WATT3D.