time-queues is a JavaScript library for organizing asynchronous multitasking and scheduled tasks. It provides efficient solutions for timing and scheduling challenges with browser-optimized queue implementations. It works seamlessly in browsers, Node.js, Bun, and Deno environments.
MicroTask (base task unit)
MicroTaskQueue (abstract base queue)
├── ListQueue (linked-list storage)
│ ├── IdleQueue (requestIdleCallback)
│ ├── FrameQueue (requestAnimationFrame)
│ ├── LimitedQueue (concurrency control)
│ └── PageWatcher (page lifecycle)
└── Scheduler (min-heap, time-based)
The library is built around a hierarchical task management system:
MicroTask: Base class for deferred execution with promise resolution capabilities. Internally usesPromise.withResolvers()when available, falling back to manual Promise construction. Tracks settled state and provides clean cancellation withCancelTaskError.MicroTaskQueue: Abstract base queue class that manages task lifecycle (enqueue, dequeue, cancel). Provides genericschedule()method that wraps functions in promise-returning tasks.ListQueue: Concrete base class using linked lists (fromlist-toolkit) for efficient O(1) task management. Manages queue processing lifecycle with start/stop mechanisms. Most specialized queues extend this class.Scheduler: ExtendsMicroTaskQueuedirectly (notListQueue). Uses a min-heap for efficient O(log n) time-based scheduling. Tasks are executed within a configurable tolerance window for performance optimization.
The library provides multiple queue implementations optimized for different use cases:
- IdleQueue: Uses
requestIdleCallback()for background task execution during browser idle periods. Configurable timeout batching for consistent performance. RequiresrequestIdleCallbackto be available in the environment. - FrameQueue: Uses
requestAnimationFrame()for animation frame-based execution. Optional time-based batching to limit execution time per frame. Processes tasks in FIFO order within each frame. - LimitedQueue: Concurrency-controlled queue that limits simultaneous task execution. Uses internal active task counter and idle waiter patterns. Asynchronous task wrapping via a module-private
wraphelper ensurestask.fn()rejections route through the same.finally()cleanup as resolutions. (Since version 1.3.0.) - PageWatcher: Monitors page lifecycle state changes (
active,passive,hidden,frozen,terminated). Registers/deregisters event listeners on resume/pause. Does not supportschedule().
- Counter: Tracks numeric values with async waiting capabilities. Supports
waitForZero()andwaitFor(fn)for condition-based waiting. Uses arrays for both zero waiters and function waiters;clearWaiters()resolves pending waiters withNaNas a "queue cleared" sentinel. - Throttler: Rate limiting based on keys using a Map to track last-seen times. Configurable throttle timeout, never-seen timeout, and vacuum period. Automatic periodic cleanup of stale entries. (Since version 1.1.0.)
- Retainer: Resource lifecycle management with retention periods. Creates resources on demand, retains them after release for a configurable period, then destroys them. Uses a simple numeric counter (not the Counter class). (Since version 1.1.0.)
All tasks are wrapped in MicroTask objects that provide promise-based interfaces:
- Promises are created lazily via
makePromise()— not in the constructor - Tasks can be resolved or canceled with proper cleanup of internal references
- Cancellation uses a custom
CancelTaskErrorexception with optional cause chaining enqueue()does not create a promise;schedule()does (calls bothenqueue()andmakePromise())cancel(error)called beforemakePromise()stores the error reason on the instance and replays it ascauseif the promise is later created. Read viatask.cancelError.resolve()called beforemakePromise()throws — guards against silent value loss that would hang any later subscriber.
ListQueue and its subclasses use a consistent pattern for queue lifecycle:
startQueue()begins processing and returns a stop function (or null)- The stop function is stored in
this.stopQueue - Queue auto-starts when a task is enqueued (if not paused and not already running)
- Queue auto-stops when empty
The library abstracts various browser APIs with graceful fallbacks:
defer()(standalone function): UsesrequestIdleCallbackwhen available, falls back tosetImmediate, thensetTimeoutIdleQueue: UsesrequestIdleCallbackdirectly (no built-in fallback)FrameQueue: UsesrequestAnimationFramedirectly (no built-in fallback)PageWatcher: Handles page lifecycle events with state transition callbacks- Feature detection patterns ensure compatibility across environments
- Task cancellation properly cleans up references (nulls
#resolve,#reject) to prevent memory leaks Counterclass provides tracking with waiting capabilities using array-based zero waiters and Set-based function waitersRetainerclass manages resource lifecycle with configurable retention periods and automatic cleanup timers- Event listener registration/deregistration in
PageWatcherprevents accumulation of handlers
- Extends
MicroTaskQueuedirectly (uses min-heap instead of linked list) - Exports
Taskclass (extendsMicroTaskwithtimeanddelayproperties) enqueue(fn, delay)accepts a function and a delay (ms or Date object)- Provides
repeat(fn, delay)for creating recurring tasks (delay is clamped to ≥1ms to prevent infinite loops) - Exports a default
schedulersingleton instance - Handles timing precision with configurable tolerance (default 4ms)
- Automatic timer rescheduling when earlier tasks are added
- Optional
onError(error, task)callback handles per-task exceptions; loop continues regardless. When unset, exceptions surface via the unhandled-rejection channel.
- Rate limiting based on keys to prevent excessive execution with per-key tracking
- Configurable timeout periods (
throttleTimeout), initial delay (neverSeenTimeout), and cleanup intervals (vacuumPeriod) isVacuuminggetter to check if vacuum interval is active- Memory-efficient tracking using Map data structure with automatic cleanup of stale entries
- Non-blocking delay implementation using
sleep()for async/await compatibility
defer(fn): Execute tasks in next tick using optimal APIs (requestIdleCallback>setImmediate>setTimeout). Also exportsscheduleDefer(fn)which returns a Promise.sleep(ms): Promise-based delay function supporting both numeric delays and Date objects with automatic conversion.throttle(fn, ms): Limit function execution rate with immediate execution on first call and trailing edge suppression.debounce(fn, ms): Delay function execution until input stabilizes with proper timer cancellation on subsequent calls.sample(fn, ms): Execute function at regular intervals with time-drift correction for consistent timing.audit(fn, ms): Execute function after specified delay with argument caching for last invocation.batch(fns, limit): Execute async operations with controlled concurrency (default limit: 4) using sliding window pattern. Accepts arrays of functions, promises, or values. (Since version 1.3.0.)
whenDomLoaded(fn): Execute callback when DOM content is loaded (or immediately if already loaded). Also exportsscheduleWhenDomLoaded(fn)(Promise-based) andremove(fn).whenLoaded(fn): Execute callback when page is fully loaded (or immediately if already loaded). Also exportsscheduleWhenLoaded(fn)(Promise-based) andremove(fn).
random-dist: Generate random numbers from various probability distributions:uniform(min, max),normal(mean, stdDev, skewness),expo(lambda),pareto(min, alpha). The skew-normal mode uses two N(0,1) samples derived from a single Box-Muller pair (cos + sin arms), per Azzalini's standard form. (Since version 1.3.0.)random-sleep: Create randomized delays:randomUniformSleep,randomNormalSleep,randomExpoSleep,randomParetoSleep(factory functions returning sleep functions), andrandomSleep(max, min)(direct Promise). (Since version 1.3.0.)
- Custom
CancelTaskErrorextends JavaScript'sErrorclass with optional cause chaining via{cause}option - Proper cleanup on task cancellation: nulls
#resolveand#reject, sets#settledto true - Integration with Promise rejection mechanisms
- Consistent error propagation through promise chains
- Graceful degradation when browser APIs are not available through feature detection
defer()uses feature detection forrequestIdleCallback,setImmediate, falls back tosetTimeoutIdleQueueandFrameQueuerequire their respective browser APIs (requestIdleCallback,requestAnimationFrame)PageWatcherrequires DOM event APIs (addEventListener,document.visibilityState,document.hasFocus)- Node.js/Bun/Deno compatibility for core scheduling, timing, and utility functions
- Minimal memory overhead through efficient data structures (linked lists, min-heaps, Maps) with O(1) and O(log n) operations
- Lazy promise creation to avoid unnecessary work
- Automatic queue start/stop based on task availability
- Proper cleanup of event listeners and timers with deterministic deregistration patterns
- Efficient heap operations for scheduling with bulk task processing within tolerance windows
import {Scheduler} from 'time-queues/Scheduler.js';
const scheduler = new Scheduler();
scheduler.enqueue(() => console.log('Delayed task'), 1000);import {IdleQueue} from 'time-queues/IdleQueue.js';
import {FrameQueue} from 'time-queues/FrameQueue.js';
// Background tasks during idle periods
const idleQueue = new IdleQueue();
idleQueue.enqueue(({deadline, task, queue}) => processBackgroundTask());
// Animation frame tasks
const frameQueue = new FrameQueue();
frameQueue.enqueue(({timeStamp, task, queue}) => animateFrame(timeStamp));import {LimitedQueue} from 'time-queues/LimitedQueue.js';
const limitedQueue = new LimitedQueue(3); // Max 3 concurrent tasks
limitedQueue.enqueue(async ({task, queue}) => await performOperation());
await limitedQueue.waitForIdle();import {Retainer} from 'time-queues/Retainer.js';
const retainer = new Retainer({
create: () => createResource(),
destroy: resource => destroyResource(resource),
retentionPeriod: 5000
});
const resource = await retainer.get();
// Use resource...
await retainer.release();- list-toolkit: Core dependency for
List(doubly-linked list) andMinHeapdata structures - tape-six: Test framework (dev dependency)
requestIdleCallback()/cancelIdleCallback()forIdleQueuerequestAnimationFrame()/cancelAnimationFrame()forFrameQueuequeueMicrotask()forPageWatcherinitialization- Page lifecycle events (
pageshow,pagehide,focus,blur,visibilitychange,resume,freeze) forPageWatcher setTimeout/clearTimeout/setInterval/clearIntervalforScheduler,Throttler,Retainer, and utility functions
The library is designed to work in browsers, Node.js, Bun, and Deno environments. Core scheduling, timing, and utility functions work across all environments. Browser-specific queues (IdleQueue, FrameQueue, PageWatcher) require their respective browser APIs.
The project uses tape-six for testing:
- Unit tests for all core components in
tests/directory (test-*.js,test-*.mjs) - CommonJS tests in
tests/directory (test-*.cjs) - Browser-specific manual tests in
tests/web/
ts-tests/directory: TypeScript typing tests that exercise tricky TS typings (generics, callback signatures, overloads, type constraints). Run vianpm run ts-testor checked statically vianpm run ts-check(tsc --noEmit)
- README — user-facing documentation
- AGENTS.md — AI agent guide
- CONTRIBUTING.md — contributor guide
- Wiki — component documentation