{"id":16773845,"url":"https://github.com/ziflex/pinterval","last_synced_at":"2026-01-16T12:28:29.299Z","repository":{"id":16371276,"uuid":"79825529","full_name":"ziflex/pinterval","owner":"ziflex","description":"Advanced setInterval","archived":false,"fork":false,"pushed_at":"2023-03-01T00:40:00.000Z","size":1275,"stargazers_count":0,"open_issues_count":2,"forks_count":0,"subscribers_count":2,"default_branch":"master","last_synced_at":"2025-02-24T04:37:42.527Z","etag":null,"topics":["async","interval","typescript","worker"],"latest_commit_sha":null,"homepage":"","language":"TypeScript","has_issues":true,"has_wiki":null,"has_pages":null,"mirror_url":null,"source_name":null,"license":"mit","status":null,"scm":"git","pull_requests_enabled":true,"icon_url":"https://github.com/ziflex.png","metadata":{"files":{"readme":"README.md","changelog":"CHANGELOG.md","contributing":null,"funding":null,"license":"LICENSE","code_of_conduct":null,"threat_model":null,"audit":null,"citation":null,"codeowners":null,"security":null,"support":null}},"created_at":"2017-01-23T16:52:57.000Z","updated_at":"2021-09-21T16:35:26.000Z","dependencies_parsed_at":"2023-01-13T18:48:58.331Z","dependency_job_id":null,"html_url":"https://github.com/ziflex/pinterval","commit_stats":null,"previous_names":[],"tags_count":8,"template":false,"template_full_name":null,"repository_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/ziflex%2Fpinterval","tags_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/ziflex%2Fpinterval/tags","releases_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/ziflex%2Fpinterval/releases","manifests_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/ziflex%2Fpinterval/manifests","owner_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners/ziflex","download_url":"https://codeload.github.com/ziflex/pinterval/tar.gz/refs/heads/master","host":{"name":"GitHub","url":"https://github.com","kind":"github","repositories_count":243903167,"owners_count":20366434,"icon_url":"https://github.com/github.png","version":null,"created_at":"2022-05-30T11:31:42.601Z","updated_at":"2022-07-04T15:15:14.044Z","host_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub","repositories_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories","repository_names_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repository_names","owners_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners"}},"keywords":["async","interval","typescript","worker"],"created_at":"2024-10-13T06:47:10.855Z","updated_at":"2026-01-16T12:28:29.284Z","avatar_url":"https://github.com/ziflex.png","language":"TypeScript","funding_links":[],"categories":[],"sub_categories":[],"readme":"# pinterval\n\n\u003e Advanced interval management for JavaScript/TypeScript\n\n[![npm version](https://badge.fury.io/js/pinterval.svg)](https://www.npmjs.com/package/pinterval)\n[![Actions Status](https://github.com/ziflex/pinterval/workflows/Node%20CI/badge.svg)](https://github.com/ziflex/pinterval/actions)\n[![License: MIT](https://img.shields.io/badge/License-MIT-yellow.svg)](https://opensource.org/licenses/MIT)\n\nA powerful and flexible interval management library that goes beyond JavaScript's native `setInterval`. Perfect for background tasks, polling, retries, and complex scheduling scenarios with built-in support for async/await, error handling, and dynamic timing strategies.\n\n## Table of Contents\n\n- [Features](#features)\n- [Installation](#installation)\n- [Quick Start](#quick-start)\n- [Why pinterval?](#why-pinterval)\n- [API Documentation](#api-documentation)\n- [Core Concepts](#core-concepts)\n  - [Interval Class](#interval-class)\n  - [Start Modes](#start-modes)\n  - [Auto-Stop Mechanism](#auto-stop-mechanism)\n  - [Error Handling](#error-handling)\n- [Helper Functions](#helper-functions)\n  - [poll](#poll)\n  - [until](#until)\n  - [retry](#retry)\n  - [times](#times)\n  - [pipeline](#pipeline)\n  - [sleep](#sleep)\n- [Duration Functions](#duration-functions)\n  - [constant](#constant)\n  - [linear](#linear)\n  - [exponential](#exponential)\n  - [fibonacci](#fibonacci)\n  - [jittered](#jittered)\n  - [decorrelatedJitter](#decorrelatedjitter)\n  - [steps](#steps)\n- [Real-World Examples](#real-world-examples)\n- [TypeScript Support](#typescript-support)\n- [Comparison with Native setInterval](#comparison-with-native-setinterval)\n- [Best Practices](#best-practices)\n- [Development](#development)\n- [Contributing](#contributing)\n- [License](#license)\n\n## Features\n\n- ✅ **Async/Await Support** - Native promise support for asynchronous operations\n- ✅ **Graceful Error Handling** - Built-in error handling with customizable recovery strategies\n- ✅ **Dynamic Intervals** - Calculate interval duration dynamically based on iteration count\n- ✅ **Auto-Stop Mechanism** - Automatically stop intervals based on return values\n- ✅ **Rich Helper Functions** - Pre-built utilities for common patterns (polling, retries, pipelines)\n- ✅ **Backoff Strategies** - Multiple built-in duration functions for sophisticated retry logic\n- ✅ **TypeScript First** - Full TypeScript support with comprehensive type definitions\n- ✅ **Zero Dependencies** - Minimal footprint with only one tiny dependency\n- ✅ **Production Ready** - Battle-tested and actively maintained\n\n## Installation\n\nInstall using your preferred package manager:\n\n```bash\n# npm\nnpm install --save pinterval\n\n# yarn\nyarn add pinterval\n\n# pnpm\npnpm add pinterval\n```\n\n## Quick Start\n\n```typescript\nimport { Interval } from 'pinterval';\n\n// Create a simple interval\nconst interval = new Interval({\n    func: () =\u003e console.log('Tick!'),\n    time: 1000\n});\n\n// Start the interval\ninterval.start();\n\n// Stop when needed\nsetTimeout(() =\u003e interval.stop(), 5000);\n```\n\n## Why pinterval?\n\nJavaScript's native `setInterval` has several limitations:\n\n- No native async/await support\n- No built-in error handling\n- Fixed intervals only (no dynamic timing)\n- No automatic cleanup on errors\n- Callback-based API\n\n`pinterval` solves all these problems with a modern, Promise-based API that's perfect for:\n\n- **Polling APIs** - Check for updates with intelligent backoff\n- **Background Tasks** - Run periodic maintenance with error recovery\n- **Retry Logic** - Implement sophisticated retry strategies\n- **Health Checks** - Monitor services with adaptive intervals\n- **Rate Limiting** - Control execution frequency dynamically\n- **Data Synchronization** - Sync data with automatic error handling\n\n## API Documentation\n\nFull API documentation is available at [http://ziflex.github.io/pinterval](http://ziflex.github.io/pinterval)\n\n## Core Concepts\n\n### Interval Class\n\nThe `Interval` class is the core building block of pinterval. It provides a flexible way to execute functions repeatedly with configurable timing and error handling.\n\n#### Basic Usage\n\n```typescript\nimport { Interval } from 'pinterval';\n\nconst interval = new Interval({\n    func: () =\u003e console.log('Tick!'),\n    time: 1000\n});\n\ninterval.start();\n\n// Stop when needed\ninterval.stop();\n```\n\n#### Constructor Parameters\n\n```typescript\ninterface Params {\n    func: (() =\u003e boolean | void) | ((counter: number) =\u003e boolean | void);\n    time: number | ((counter: number) =\u003e number);\n    start?: 'immediate' | 'delayed';\n    onError?: (err: Error) =\u003e boolean | void;\n}\n```\n\n- **func** - Function to execute on each interval. Can be sync or async (returns Promise)\n- **time** - Interval duration in milliseconds or a function that calculates it dynamically\n- **start** - When to execute the first tick: `'delayed'` (default) waits for first timeout, `'immediate'` executes immediately\n- **onError** - Optional error handler. Return `true` to continue, `false` to stop\n\n#### Methods\n\n- **start()** - Starts the interval. Throws if already running.\n- **stop()** - Stops the interval. Throws if already stopped.\n- **isRunning** - Property that returns `true` if the interval is currently running.\n\n### Start Modes\n\nControl when your interval executes for the first time:\n\n```typescript\n// Delayed start (default): waits for timeout before first execution\nconst delayedInterval = new Interval({\n    func: () =\u003e console.log('First execution after 1 second'),\n    time: 1000,\n    start: 'delayed' // or omit this, it's the default\n});\n\n// Immediate start: executes immediately, then waits for timeout\nconst immediateInterval = new Interval({\n    func: () =\u003e console.log('Executes immediately!'),\n    time: 1000,\n    start: 'immediate'\n});\n```\n\n### Auto-Stop Mechanism\n\nIf your function returns `false`, the interval automatically stops. This is useful for self-terminating intervals.\n\n```typescript\nimport { Interval } from 'pinterval';\n\nlet counter = 0;\nconst interval = new Interval({\n    func: () =\u003e {\n        counter++;\n        console.log(`Tick ${counter}`);\n        \n        // Stop after 10 ticks\n        return counter \u003c 10;\n    },\n    time: 1000\n});\n\ninterval.start();\n// Will automatically stop after 10 executions\n```\n\n### Error Handling\n\nComprehensive error handling with both synchronous and asynchronous error handlers:\n\n```typescript\nimport { Interval } from 'pinterval';\n\n// Synchronous error handler\nconst interval = new Interval({\n    func: () =\u003e {\n        // This might throw\n        riskyOperation();\n    },\n    time: 1000,\n    onError: (err) =\u003e {\n        console.error('Error occurred:', err);\n        \n        // Return false to stop, true to continue\n        if (err instanceof FatalError) {\n            return false; // Stop interval\n        }\n        \n        return true; // Continue with next tick\n    }\n});\n\n// Asynchronous error handler\nconst asyncInterval = new Interval({\n    func: async () =\u003e {\n        const response = await fetch('https://api.example.com/data');\n        return response.ok;\n    },\n    time: 5000,\n    onError: async (err: Error) =\u003e {\n        // Log error to remote service\n        await fetch('https://logging-service.com/log', {\n            method: 'POST',\n            body: JSON.stringify({ error: err.message })\n        });\n        \n        // Decide whether to continue\n        return err.message !== 'FATAL';\n    }\n});\n```\n\n**Error Handler Return Values:**\n\n- `true` - Continue interval execution (schedules next tick)\n- `false` - Stop interval execution\n- `undefined` or no return - Stops interval execution\n- If error handler itself throws, the interval stops\n\n### Async Support\n\nNative support for asynchronous functions with proper race condition prevention:\n\n```typescript\nimport { Interval } from 'pinterval';\n\nconst interval = new Interval({\n    func: async () =\u003e {\n        // The next tick won't start until this Promise resolves\n        const data = await fetch('https://api.example.com/status');\n        const json = await data.json();\n        \n        console.log('Status:', json.status);\n        \n        // Can return false to stop\n        return json.status !== 'completed';\n    },\n    time: 2000\n});\n\ninterval.start();\n```\n\n**Key Points:**\n\n- Each tick waits for the Promise to resolve before scheduling the next one\n- No race conditions - async operations won't overlap\n- Interval timing starts **after** async operation completes\n- Return `false` from async function to stop the interval\n\n### Dynamic Duration\n\nCalculate interval duration dynamically based on the iteration count:\n\n```typescript\nimport { Interval } from 'pinterval';\n\n// Exponential backoff\nconst interval = new Interval({\n    func: () =\u003e console.log('Tick!'),\n    time: (counter) =\u003e {\n        const minTimeout = 500;\n        const maxTimeout = 10000;\n        const timeout = Math.round(minTimeout * Math.pow(2, counter - 1));\n        \n        return Math.min(timeout, maxTimeout);\n    }\n});\n\ninterval.start();\n// Executions at: 500ms, 1000ms, 2000ms, 4000ms, 8000ms, 10000ms, 10000ms...\n```\n\n**Counter Parameter:**\n\n- Starts at `1` for the first execution\n- Increments with each tick\n- Useful for implementing backoff strategies\n\nFor complex timing strategies, see the [Duration Functions](#duration-functions) section.\n\n## Helper Functions\n\npinterval provides several high-level helper functions for common patterns. All helpers are Promise-based and work seamlessly with async/await.\n\n### poll\n\nRepeatedly checks a condition until it returns `true`. Perfect for waiting on asynchronous operations.\n\n```typescript\nimport { poll } from 'pinterval';\n\n// Wait for a condition to be true\nawait poll(async () =\u003e {\n    const status = await checkStatus();\n    return status === 'ready';\n}, 1000);\n\nconsole.log('Condition met!');\n```\n\n**Signature:**\n```typescript\nfunction poll(\n    predicate: () =\u003e boolean | Promise\u003cboolean\u003e,\n    timeout: number | ((counter: number) =\u003e number),\n    start?: 'immediate' | 'delayed'\n): Promise\u003cvoid\u003e\n```\n\n**Parameters:**\n\n- **predicate** - Function that returns `true` when condition is met\n- **timeout** - Interval duration in milliseconds or duration function\n- **start** - Start mode: `'delayed'` (default) or `'immediate'`\n\n**Example with immediate start:**\n\n```typescript\n// Check immediately, then every 5 seconds\nawait poll(\n    async () =\u003e (await fetch('/api/status')).ok,\n    5000,\n    'immediate'\n);\n```\n\n### until\n\nSimilar to `poll`, but returns the value from the predicate once it's defined (not `undefined`).\n\n```typescript\nimport { until } from 'pinterval';\n\n// Wait until we get actual data\nconst data = await until(async () =\u003e {\n    const response = await fetch('/api/data');\n    if (!response.ok) return undefined;\n    \n    const json = await response.json();\n    return json.data; // Returns value once available\n}, 2000);\n\nconsole.log('Data received:', data);\n```\n\n**Signature:**\n```typescript\nfunction until\u003cT\u003e(\n    predicate: () =\u003e T | undefined | Promise\u003cT | undefined\u003e,\n    timeout: number | ((counter: number) =\u003e number),\n    start?: 'immediate' | 'delayed'\n): Promise\u003cT\u003e\n```\n\n**Key Difference from poll:**\n\n- `poll` - Waits for `true`, returns `void`\n- `until` - Waits for non-`undefined` value, returns that value\n\n### retry\n\nExecutes a function with retry logic. Stops after reaching the maximum attempts or when a truthy value is returned.\n\n```typescript\nimport { retry } from 'pinterval';\n\n// Retry up to 5 times with 2 second intervals\nconst result = await retry(\n    async (attempt) =\u003e {\n        const response = await fetch('/api/resource');\n        if (response.ok) {\n            return await response.json();\n        }\n        return undefined; // Will retry\n    },\n    5,      // max attempts\n    2000    // interval between attempts\n);\n```\n\n**Signature:**\n```typescript\nfunction retry\u003cT\u003e(\n    predicate: (attempt: number) =\u003e T | Promise\u003cT\u003e,\n    attempts: number,\n    timeout: number | ((counter: number) =\u003e number),\n    start?: 'immediate' | 'delayed'\n): Promise\u003cT\u003e\n```\n\n**Parameters:**\n\n- **predicate** - Function to retry that receives the current attempt number. Return `undefined` to retry, or a value to resolve\n- **attempts** - Maximum number of retry attempts\n- **timeout** - Interval between retries\n- **start** - Start mode: `'immediate'` (default) or `'delayed'`\n\n**With exponential backoff:**\n\n```typescript\nimport { retry, duration } from 'pinterval';\n\nconst result = await retry(\n    async (attempt) =\u003e {\n        try {\n            return await fetchData();\n        } catch {\n            return undefined; // Retry on error\n        }\n    },\n    10,\n    duration.exponential(1000, 30000) // 1s, 2s, 4s, 8s, 16s, 30s, 30s...\n);\n```\n\n### times\n\nExecutes a function a specific number of times with an interval between executions.\n\n```typescript\nimport { times } from 'pinterval';\n\n// Execute 5 times with 1 second between executions\nawait times(\n    async (counter) =\u003e {\n        console.log(`Execution ${counter}`);\n        await updateMetrics(counter);\n    },\n    5,\n    1000\n);\n\nconsole.log('All executions completed!');\n```\n\n**Signature:**\n```typescript\nfunction times(\n    predicate: (counter: number) =\u003e void | Promise\u003cvoid\u003e,\n    amount: number,\n    timeout: number | ((counter: number) =\u003e number),\n    start?: 'immediate' | 'delayed'\n): Promise\u003cvoid\u003e\n```\n\n**Parameters:**\n\n- **predicate** - Function to execute. Receives counter (1-based) as parameter\n- **amount** - Number of times to execute\n- **timeout** - Interval between executions\n- **start** - Start mode: `'delayed'` (default) or `'immediate'`\n\n### pipeline\n\nSequentially executes an array of functions with intervals between them. Each function receives the output of the previous one.\n\n```typescript\nimport { pipeline } from 'pinterval';\n\nconst result = await pipeline([\n    () =\u003e 1,\n    (x) =\u003e x * 2,      // receives 1, returns 2\n    (x) =\u003e x + 3,      // receives 2, returns 5\n    (x) =\u003e x * 4       // receives 5, returns 20\n], 100);\n\nconsole.log(result); // 20\n```\n\n**Signature:**\n```typescript\nfunction pipeline(\n    predicates: Array\u003c(data: any) =\u003e any | Promise\u003cany\u003e\u003e,\n    timeout: number | ((counter: number) =\u003e number),\n    start?: 'immediate' | 'delayed'\n): Promise\u003cany\u003e\n```\n\n**Important Notes:**\n\n- First function executes with 0 timeout when `start: 'immediate'` (default)\n- Each subsequent function waits for the timeout\n- Output of each function is passed to the next\n- Perfect for multi-stage data processing\n\n**Async pipeline example:**\n\n```typescript\nimport { pipeline } from 'pinterval';\n\nconst result = await pipeline([\n    async () =\u003e await fetch('/api/users'),\n    async (response) =\u003e await response.json(),\n    async (users) =\u003e users.filter(u =\u003e u.active),\n    async (activeUsers) =\u003e {\n        await saveToDatabase(activeUsers);\n        return activeUsers.length;\n    }\n], 500);\n\nconsole.log(`Processed ${result} active users`);\n```\n\n### sleep\n\nSimple utility to pause execution for a specified duration.\n\n```typescript\nimport { sleep } from 'pinterval';\n\nconsole.log('Starting...');\nawait sleep(2000);\nconsole.log('2 seconds later...');\n```\n\n**Signature:**\n```typescript\nfunction sleep(time: number): Promise\u003cvoid\u003e\n```\n\n## Duration Functions\n\nStarting with v3.7.0, pinterval includes a collection of duration calculation functions for dynamic interval scheduling. These are perfect for implementing sophisticated retry and backoff strategies.\n\nAll duration functions are available under the `duration` namespace and follow this signature:\n\n```typescript\ntype DurationFunction = (counter: number) =\u003e number;\n```\n\nThe `counter` parameter starts at 1 for the first execution and increments with each tick.\n\n### constant\n\nReturns the same duration for every execution. Useful for fixed intervals.\n\n```typescript\nimport { Interval, duration } from 'pinterval';\n\nconst interval = new Interval({\n    func: () =\u003e console.log('Tick!'),\n    time: duration.constant(1000)\n});\n\ninterval.start();\n// Executes every 1000ms: 1000, 1000, 1000, 1000...\n```\n\n**Signature:**\n```typescript\nfunction constant(ms: number): DurationFunction\n```\n\n**Use Cases:**\n\n- Fixed interval polling\n- Regular health checks\n- Consistent retry delays\n\n### linear\n\nIncreases duration linearly by a fixed increment on each iteration.\n\n```typescript\nimport { Interval, duration } from 'pinterval';\n\nconst interval = new Interval({\n    func: () =\u003e console.log('Tick!'),\n    time: duration.linear(100, 50)\n});\n\ninterval.start();\n// Executes at: 100ms, 150ms, 200ms, 250ms, 300ms...\n```\n\n**Signature:**\n```typescript\nfunction linear(initial: number, increment: number): DurationFunction\n```\n\n**Parameters:**\n\n- **initial** - Starting duration in milliseconds\n- **increment** - Amount to increase (or decrease if negative) per iteration\n\n**Use Cases:**\n\n- Gradual slowdown for polling\n- Progressive backoff with predictable growth\n- Testing and debugging scenarios\n\n**Decreasing intervals:**\n\n```typescript\n// Start fast, get slower by 100ms each time\nconst decreasing = duration.linear(2000, -100);\n// Executes at: 2000ms, 1900ms, 1800ms, 1700ms...\n```\n\n### exponential\n\nDoubles the duration on each iteration with an optional maximum cap. This is the standard backoff strategy used in many systems.\n\n```typescript\nimport { retry, duration } from 'pinterval';\n\n// Exponential backoff for retries\nconst result = await retry(\n    async (attempt) =\u003e await fetchData(),\n    10,\n    duration.exponential(100, 10000)\n);\n\n// Executes at: 100ms, 200ms, 400ms, 800ms, 1600ms, 3200ms, 6400ms, 10000ms, 10000ms...\n```\n\n**Signature:**\n```typescript\nfunction exponential(initial: number, max?: number): DurationFunction\n```\n\n**Parameters:**\n\n- **initial** - Starting duration in milliseconds\n- **max** - Optional maximum duration cap\n\n**Use Cases:**\n\n- Standard retry backoff strategy\n- Network request retries\n- Database reconnection attempts\n- API rate limiting\n\n**Without cap:**\n\n```typescript\n// Unbounded exponential growth\nconst uncapped = duration.exponential(100);\n// Executes at: 100ms, 200ms, 400ms, 800ms, 1600ms, 3200ms, 6400ms...\n```\n\n### fibonacci\n\nUses the Fibonacci sequence for duration calculation. Provides gentler growth than exponential backoff.\n\n```typescript\nimport { Interval, duration } from 'pinterval';\n\nconst interval = new Interval({\n    func: () =\u003e console.log('Tick!'),\n    time: duration.fibonacci(100)\n});\n\ninterval.start();\n// Executes at: 100ms, 100ms, 200ms, 300ms, 500ms, 800ms, 1300ms...\n```\n\n**Signature:**\n```typescript\nfunction fibonacci(initial: number): DurationFunction\n```\n\n**Parameters:**\n\n- **initial** - Base duration in milliseconds (used for F(0) and F(1))\n\n**Use Cases:**\n\n- Gentler backoff than exponential\n- Natural growth patterns\n- Alternative retry strategy when exponential is too aggressive\n\n### jittered\n\nAdds randomness to exponential backoff to prevent the \"thundering herd\" problem where multiple clients retry simultaneously.\n\n```typescript\nimport { retry, duration } from 'pinterval';\n\n// Add ±10% randomness to prevent synchronized retries\nconst result = await retry(\n    async () =\u003e await fetchData(),\n    10,\n    duration.jittered(1000, 30000, 0.1)\n);\n\n// Example execution times (with ±10% jitter):\n// ~1000ms (900-1100), ~2000ms (1800-2200), ~4000ms (3600-4400)...\n```\n\n**Signature:**\n```typescript\nfunction jittered(\n    initial: number,\n    max?: number,\n    jitterFactor?: number\n): DurationFunction\n```\n\n**Parameters:**\n\n- **initial** - Starting duration in milliseconds\n- **max** - Optional maximum duration cap\n- **jitterFactor** - Amount of randomness (default: 0.1 = ±10%)\n\n**Use Cases:**\n\n- Distributed system retries\n- Preventing thundering herd problem\n- Load distribution across time\n- API rate limiting with multiple clients\n\n**Custom jitter:**\n\n```typescript\n// ±25% randomness\nconst highJitter = duration.jittered(1000, 10000, 0.25);\n\n// ±5% randomness  \nconst lowJitter = duration.jittered(1000, 10000, 0.05);\n```\n\n### decorrelatedJitter\n\nAWS-recommended jitter strategy where each delay is based on the previous delay, not the iteration count. This is a stateful function.\n\n```typescript\nimport { retry, duration } from 'pinterval';\n\n// AWS-style decorrelated jitter\nconst result = await retry(\n    async () =\u003e await fetchData(),\n    10,\n    duration.decorrelatedJitter(100, 10000)\n);\n\n// Each delay is random(0, previous_delay * 3), capped at max\n// Provides excellent distribution for distributed systems\n```\n\n**Signature:**\n```typescript\nfunction decorrelatedJitter(initial: number, max: number): DurationFunction\n```\n\n**Parameters:**\n\n- **initial** - Starting duration in milliseconds\n- **max** - Maximum duration cap (required)\n\n**Use Cases:**\n\n- AWS SDK retry logic\n- Best-practice distributed retries\n- Optimal backoff with jitter\n- Production-ready retry strategies\n\n**Important Note:**\n\nThis function is stateful - each instance maintains internal state. Create a new instance for each interval:\n\n```typescript\n// ✅ Correct: new instance per interval\nconst interval1 = new Interval({\n    func: task1,\n    time: duration.decorrelatedJitter(100, 5000)\n});\n\nconst interval2 = new Interval({\n    func: task2,\n    time: duration.decorrelatedJitter(100, 5000)\n});\n\n// ❌ Wrong: sharing instance causes unexpected behavior\nconst sharedDuration = duration.decorrelatedJitter(100, 5000);\nconst interval3 = new Interval({ func: task1, time: sharedDuration });\nconst interval4 = new Interval({ func: task2, time: sharedDuration });\n```\n\n### steps\n\nReturns different durations based on counter thresholds. Perfect for phase-based intervals that change behavior over time.\n\n```typescript\nimport { Interval, duration } from 'pinterval';\n\nconst interval = new Interval({\n    func: () =\u003e console.log('Tick!'),\n    time: duration.steps([\n        { threshold: 0, duration: 100 },   // Fast for first 5\n        { threshold: 5, duration: 500 },   // Medium for 5-10\n        { threshold: 10, duration: 2000 }  // Slow after 10\n    ])\n});\n\ninterval.start();\n// Counter 1-4: 100ms\n// Counter 5-9: 500ms  \n// Counter 10+: 2000ms\n```\n\n**Signature:**\n```typescript\nfunction steps(\n    thresholds: Array\u003c{ threshold: number; duration: number }\u003e\n): DurationFunction\n```\n\n**Parameters:**\n\n- **thresholds** - Array of threshold/duration pairs (order doesn't matter, will be sorted)\n\n**Use Cases:**\n\n- Phase-based intervals (fast → medium → slow)\n- Polling that changes behavior over time\n- Different retry strategies per attempt range\n- Multi-stage backoff\n\n**Complex example:**\n\n```typescript\nimport { retry, duration } from 'pinterval';\n\n// Aggressive at first, then back off\nconst result = await retry(\n    async (attempt) =\u003e await fetchData(),\n    20,\n    duration.steps([\n        { threshold: 0, duration: 100 },   // First 3 attempts: fast (100ms)\n        { threshold: 3, duration: 500 },   // Attempts 3-6: medium (500ms)\n        { threshold: 6, duration: 2000 },  // Attempts 6-10: slow (2s)\n        { threshold: 10, duration: 5000 }  // Attempts 10+: very slow (5s)\n    ])\n);\n```\n\n## Real-World Examples\n\n### Health Check with Exponential Backoff\n\nMonitor a service health endpoint with intelligent backoff when failures occur:\n\n```typescript\nimport { Interval, duration } from 'pinterval';\n\nlet consecutiveFailures = 0;\n\nconst healthCheck = new Interval({\n    func: async () =\u003e {\n        try {\n            const response = await fetch('https://api.example.com/health');\n            \n            if (response.ok) {\n                consecutiveFailures = 0;\n                console.log('✓ Service is healthy');\n                return true;\n            }\n            \n            consecutiveFailures++;\n            console.log(`✗ Service unhealthy (${consecutiveFailures} failures)`);\n            return true;\n        } catch (error) {\n            consecutiveFailures++;\n            console.error(`✗ Health check failed: ${error.message}`);\n            return consecutiveFailures \u003c 10; // Stop after 10 failures\n        }\n    },\n    time: (counter) =\u003e {\n        // Normal polling: 5s, on failure: exponential backoff up to 60s\n        if (consecutiveFailures === 0) return 5000;\n        return Math.min(5000 * Math.pow(2, consecutiveFailures - 1), 60000);\n    },\n    start: 'immediate'\n});\n\nhealthCheck.start();\n```\n\n### API Polling with Conditional Stop\n\nPoll an API until a specific condition is met:\n\n```typescript\nimport { poll } from 'pinterval';\n\nasync function waitForJobCompletion(jobId: string) {\n    console.log(`Waiting for job ${jobId} to complete...`);\n    \n    await poll(async () =\u003e {\n        const response = await fetch(`/api/jobs/${jobId}`);\n        const job = await response.json();\n        \n        console.log(`Job status: ${job.status}`);\n        \n        if (job.status === 'completed') {\n            console.log('Job completed successfully!');\n            return true;\n        }\n        \n        if (job.status === 'failed') {\n            throw new Error('Job failed!');\n        }\n        \n        return false; // Keep polling\n    }, 2000, 'immediate');\n}\n\n// Usage\nawait waitForJobCompletion('job-123');\n```\n\n### Retry with Fallback Strategies\n\nImplement sophisticated retry logic with multiple strategies:\n\n```typescript\nimport { retry, duration } from 'pinterval';\n\nasync function fetchWithRetry(url: string) {\n    // Try primary endpoint with exponential backoff\n    try {\n        return await retry(\n            async (attempt) =\u003e {\n                const response = await fetch(url);\n                if (!response.ok) return undefined;\n                return await response.json();\n            },\n            5,\n            duration.exponential(1000, 10000),\n            'immediate'\n        );\n    } catch (primaryError) {\n        console.warn('Primary endpoint failed, trying backup...');\n        \n        // Fall back to backup endpoint with linear backoff\n        return await retry(\n            async (attempt) =\u003e {\n                const response = await fetch(url.replace('api', 'api-backup'));\n                if (!response.ok) return undefined;\n                return await response.json();\n            },\n            3,\n            duration.linear(2000, 1000),\n            'immediate'\n        );\n    }\n}\n```\n\n### Rate-Limited API Client\n\nImplement a rate-limited API client that respects API limits:\n\n```typescript\nimport { Interval } from 'pinterval';\n\nclass RateLimitedClient {\n    private queue: Array\u003c() =\u003e Promise\u003cany\u003e\u003e = [];\n    private interval: Interval;\n    \n    constructor(requestsPerSecond: number) {\n        const delay = 1000 / requestsPerSecond;\n        \n        this.interval = new Interval({\n            func: async () =\u003e {\n                if (this.queue.length === 0) {\n                    return true; // Keep running\n                }\n                \n                const task = this.queue.shift();\n                if (task) {\n                    await task();\n                }\n                \n                return true;\n            },\n            time: delay,\n            start: 'immediate'\n        });\n        \n        this.interval.start();\n    }\n    \n    async request(url: string): Promise\u003cResponse\u003e {\n        return new Promise((resolve, reject) =\u003e {\n            this.queue.push(async () =\u003e {\n                try {\n                    const response = await fetch(url);\n                    resolve(response);\n                } catch (error) {\n                    reject(error);\n                }\n            });\n        });\n    }\n    \n    stop() {\n        this.interval.stop();\n    }\n}\n\n// Usage: max 10 requests per second\nconst client = new RateLimitedClient(10);\n\n// All requests are automatically rate-limited\nconst responses = await Promise.all([\n    client.request('/api/users/1'),\n    client.request('/api/users/2'),\n    client.request('/api/users/3'),\n    // ... more requests\n]);\n```\n\n### Database Connection Retry with Jitter\n\nPrevent thundering herd when multiple services try to reconnect to a database:\n\n```typescript\nimport { retry, duration } from 'pinterval';\n\nasync function connectToDatabase(config: DbConfig) {\n    console.log('Attempting to connect to database...');\n    \n    return await retry(\n        async (attempt) =\u003e {\n            try {\n                const connection = await createConnection(config);\n                await connection.ping();\n                console.log('✓ Database connected');\n                return connection;\n            } catch (error) {\n                console.log(`✗ Connection failed (attempt ${attempt}): ${error.message}, retrying...`);\n                return undefined;\n            }\n        },\n        10,\n        duration.jittered(1000, 30000, 0.2), // ±20% jitter\n        'immediate'\n    );\n}\n```\n\n### Multi-Stage Data Processing Pipeline\n\nProcess data through multiple stages with delays:\n\n```typescript\nimport { pipeline } from 'pinterval';\n\nasync function processUserData(userId: string) {\n    const result = await pipeline([\n        // Stage 1: Fetch user data\n        async () =\u003e {\n            console.log('Stage 1: Fetching user data...');\n            const response = await fetch(`/api/users/${userId}`);\n            return await response.json();\n        },\n        \n        // Stage 2: Enrich with additional data\n        async (user) =\u003e {\n            console.log('Stage 2: Enriching data...');\n            const orders = await fetch(`/api/users/${userId}/orders`);\n            return { ...user, orders: await orders.json() };\n        },\n        \n        // Stage 3: Calculate analytics\n        async (userData) =\u003e {\n            console.log('Stage 3: Computing analytics...');\n            return {\n                ...userData,\n                analytics: {\n                    totalOrders: userData.orders.length,\n                    totalSpent: userData.orders.reduce((sum, o) =\u003e sum + o.amount, 0)\n                }\n            };\n        },\n        \n        // Stage 4: Save to cache\n        async (enrichedData) =\u003e {\n            console.log('Stage 4: Caching results...');\n            await saveToCache(`user:${userId}`, enrichedData);\n            return enrichedData;\n        }\n    ], 500); // 500ms between stages\n    \n    console.log('Pipeline completed!');\n    return result;\n}\n```\n\n### Scheduled Background Task\n\nRun a background cleanup task with dynamic timing:\n\n```typescript\nimport { Interval, duration } from 'pinterval';\n\nconst cleanupTask = new Interval({\n    func: async (counter) =\u003e {\n        console.log(`Running cleanup task (iteration ${counter})...`);\n        \n        try {\n            // Clean up old records\n            const deleted = await deleteOldRecords();\n            console.log(`✓ Cleaned up ${deleted} old records`);\n            \n            // Clean up temporary files\n            await cleanupTempFiles();\n            console.log('✓ Temporary files cleaned');\n            \n            return true; // Continue running\n        } catch (error) {\n            console.error(`✗ Cleanup failed: ${error.message}`);\n            return true; // Continue despite errors\n        }\n    },\n    time: duration.steps([\n        { threshold: 0, duration: 60000 },      // First hour: every minute\n        { threshold: 60, duration: 300000 },    // Hours 1-5: every 5 minutes\n        { threshold: 300, duration: 3600000 }   // After 5 hours: every hour\n    ]),\n    start: 'delayed',\n    onError: async (err) =\u003e {\n        // Log error to monitoring service\n        await logError('cleanup-task', err);\n        return true; // Continue running\n    }\n});\n\ncleanupTask.start();\n```\n\n## TypeScript Support\n\npinterval is written in TypeScript and provides full type definitions out of the box. No need for `@types/*` packages!\n\n### Type-Safe Intervals\n\n```typescript\nimport { Interval, Params, IntervalFunction } from 'pinterval';\n\n// Type-safe interval function\nconst myFunction: IntervalFunction = (counter) =\u003e {\n    console.log(`Tick ${counter}`);\n    return counter \u003c 10;\n};\n\n// Type-safe parameters\nconst params: Params = {\n    func: myFunction,\n    time: 1000,\n    start: 'immediate',\n    onError: (err: Error) =\u003e {\n        console.error(err);\n        return false;\n    }\n};\n\nconst interval = new Interval(params);\n```\n\n### Generic Return Types\n\nHelper functions support generic types for type-safe return values:\n\n```typescript\nimport { until, retry } from 'pinterval';\n\ninterface User {\n    id: string;\n    name: string;\n    email: string;\n}\n\n// Type-safe until\nconst user = await until\u003cUser\u003e(async () =\u003e {\n    const response = await fetch('/api/user');\n    if (!response.ok) return undefined;\n    return await response.json(); // Typed as User\n}, 1000);\n\n// user is typed as User\nconsole.log(user.email);\n\n// Type-safe retry\ninterface ApiResponse {\n    success: boolean;\n    data: any;\n}\n\nconst result = await retry\u003cApiResponse\u003e(\n    async (attempt) =\u003e {\n        const response = await fetch('/api/data');\n        if (!response.ok) return undefined;\n        return await response.json();\n    },\n    5,\n    2000\n);\n```\n\n### Custom Duration Functions\n\nCreate type-safe duration functions:\n\n```typescript\nimport { DurationFunction, Interval } from 'pinterval';\n\n// Custom duration function with full type safety\nconst customDuration: DurationFunction = (counter: number): number =\u003e {\n    if (counter \u003c= 3) return 1000;\n    if (counter \u003c= 6) return 2000;\n    return 5000;\n};\n\nconst interval = new Interval({\n    func: () =\u003e console.log('Tick!'),\n    time: customDuration\n});\n```\n\n## Comparison with Native setInterval\n\nHere's why you might choose pinterval over native `setInterval`:\n\n| Feature | Native setInterval | pinterval |\n|---------|-------------------|-----------|\n| **Async/Await Support** | ❌ No native support | ✅ Built-in Promise support |\n| **Error Handling** | ❌ Errors crash the interval | ✅ Graceful error handling with recovery |\n| **Dynamic Intervals** | ❌ Fixed interval only | ✅ Calculate interval per iteration |\n| **Auto-Stop** | ❌ Manual management only | ✅ Automatic stop on conditions |\n| **Backoff Strategies** | ❌ Not supported | ✅ Multiple built-in strategies |\n| **Race Conditions** | ❌ Can overlap with async code | ✅ Prevents overlapping execution |\n| **Helper Functions** | ❌ Build your own | ✅ poll, retry, until, times, pipeline |\n| **TypeScript** | ⚠️ Basic types only | ✅ Full TypeScript support |\n| **API** | ⚠️ Callback-based | ✅ Modern Promise-based API |\n\n### Migration Example\n\n**Before (native setInterval):**\n\n```javascript\nlet intervalId;\nlet attempts = 0;\n\nintervalId = setInterval(async () =\u003e {\n    try {\n        attempts++;\n        const response = await fetch('/api/status');\n        const data = await response.json();\n        \n        if (data.ready) {\n            clearInterval(intervalId);\n            console.log('Ready!');\n        }\n        \n        if (attempts \u003e= 10) {\n            clearInterval(intervalId);\n            throw new Error('Max attempts reached');\n        }\n    } catch (error) {\n        clearInterval(intervalId);\n        console.error('Error:', error);\n    }\n}, 2000);\n```\n\n**After (pinterval):**\n\n```typescript\nimport { retry } from 'pinterval';\n\ntry {\n    await retry(async (attempt) =\u003e {\n        const response = await fetch('/api/status');\n        const data = await response.json();\n        return data.ready ? data : undefined;\n    }, 10, 2000);\n    \n    console.log('Ready!');\n} catch (error) {\n    console.error('Error:', error);\n}\n```\n\n## Best Practices\n\n### 1. Choose the Right Helper Function\n\n- Use `poll` when waiting for a boolean condition\n- Use `until` when you need to return a value\n- Use `retry` for operations with a maximum attempt limit\n- Use `times` for a fixed number of executions\n- Use `pipeline` for sequential multi-stage processing\n- Use `Interval` class for complex custom scenarios\n\n### 2. Handle Errors Appropriately\n\nAlways provide an error handler for production code:\n\n```typescript\nconst interval = new Interval({\n    func: async () =\u003e {\n        await riskyOperation();\n    },\n    time: 5000,\n    onError: async (err) =\u003e {\n        // Log to monitoring service\n        await logError(err);\n        \n        // Decide based on error type\n        if (err instanceof NetworkError) {\n            return true; // Retry on network errors\n        }\n        \n        return false; // Stop on other errors\n    }\n});\n```\n\n### 3. Use Appropriate Backoff Strategies\n\n- **Constant**: Simple polling with no rate limiting concerns\n- **Linear**: Gradually reduce load over time\n- **Exponential**: Standard retry strategy, most commonly used\n- **Fibonacci**: Gentler than exponential, good for user-facing features\n- **Jittered**: Distributed systems with multiple clients\n- **DecorrelatedJitter**: Production-grade distributed systems (AWS recommendation)\n- **Steps**: Different strategies for different phases\n\n### 4. Prevent Memory Leaks\n\nAlways stop intervals when they're no longer needed:\n\n```typescript\nclass MyComponent {\n    private interval: Interval;\n    \n    start() {\n        this.interval = new Interval({\n            func: () =\u003e this.updateData(),\n            time: 5000\n        });\n        this.interval.start();\n    }\n    \n    // Clean up when component unmounts\n    cleanup() {\n        if (this.interval?.isRunning) {\n            this.interval.stop();\n        }\n    }\n}\n```\n\n### 5. Use Immediate Start When Appropriate\n\nFor retries and initial checks, use `'immediate'` start mode:\n\n```typescript\n// ✅ Good: Check immediately, then retry\nawait retry(fetchData, 5, 2000, 'immediate');\n\n// ❌ Less ideal: Unnecessary wait before first attempt\nawait retry(fetchData, 5, 2000, 'delayed');\n```\n\n### 6. Test with Shorter Intervals\n\nUse shorter timeouts during testing:\n\n```typescript\nconst timeout = process.env.NODE_ENV === 'test' ? 100 : 5000;\n\nconst interval = new Interval({\n    func: myFunction,\n    time: timeout\n});\n```\n\n### 7. Combine Helpers for Complex Scenarios\n\n```typescript\n// Wait for service to be ready, then start processing\nawait poll(async () =\u003e await isServiceReady(), 1000);\n\n// Now run the main task with retries\nawait times(async (counter) =\u003e {\n    await retry(async () =\u003e await processItem(counter), 3, 1000);\n}, 10, 5000);\n```\n\n## Development\n\n### Building the Project\n\n```bash\n# Install dependencies\nnpm install\n\n# Build the project\nnpm run build\n\n# The compiled JavaScript will be in the lib/ directory\n```\n\n### Running Tests\n\n```bash\n# Run all tests\nnpm test\n\n# Run tests in watch mode\nnpm run test:watch\n```\n\n### Linting\n\n```bash\n# Lint the code\nnpm run lint\n\n# Format code\nnpm run fmt\n```\n\n### Generating Documentation\n\n```bash\n# Generate TypeDoc documentation\nnpm run doc\n\n# Documentation will be generated in docs/ directory\n```\n\n## Contributing\n\nContributions are welcome! Please feel free to submit a Pull Request. For major changes, please open an issue first to discuss what you would like to change.\n\n### Steps to Contribute\n\n1. Fork the repository\n2. Create your feature branch (`git checkout -b feature/amazing-feature`)\n3. Commit your changes (`git commit -m 'Add some amazing feature'`)\n4. Push to the branch (`git push origin feature/amazing-feature`)\n5. Open a Pull Request\n\n### Guidelines\n\n- Follow the existing code style\n- Add tests for new features\n- Update documentation as needed\n- Ensure all tests pass before submitting\n\n## License\n\nThis project is licensed under the MIT License - see the [LICENSE](LICENSE) file for details.\n\n## Credits\n\nCreated and maintained by [Tim Voronov](https://github.com/ziflex)\n\n## Links\n\n- [npm package](https://www.npmjs.com/package/pinterval)\n- [API Documentation](http://ziflex.github.io/pinterval)\n- [GitHub Repository](https://github.com/ziflex/pinterval)\n- [Issue Tracker](https://github.com/ziflex/pinterval/issues)\n- [Changelog](CHANGELOG.md)\n","project_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fziflex%2Fpinterval","html_url":"https://awesome.ecosyste.ms/projects/github.com%2Fziflex%2Fpinterval","lists_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fziflex%2Fpinterval/lists"}