{"id":27355514,"url":"https://github.com/suhaotian/async-plugins","last_synced_at":"2026-02-17T17:01:42.190Z","repository":{"id":286050372,"uuid":"960210794","full_name":"suhaotian/async-plugins","owner":"suhaotian","description":"Async operations: async retry / async cache / async dedupe / async poll","archived":false,"fork":false,"pushed_at":"2025-04-06T00:33:07.000Z","size":117,"stargazers_count":1,"open_issues_count":0,"forks_count":0,"subscribers_count":1,"default_branch":"main","last_synced_at":"2025-08-20T06:08:06.657Z","etag":null,"topics":["async-cache","async-dedupe","async-poll","async-queue","async-retry"],"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/suhaotian.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,"governance":null,"roadmap":null,"authors":null,"dei":null,"publiccode":null,"codemeta":null,"zenodo":null}},"created_at":"2025-04-04T03:36:24.000Z","updated_at":"2025-04-06T00:33:09.000Z","dependencies_parsed_at":null,"dependency_job_id":"74bff659-2628-44ca-a58b-5da30ef17bbd","html_url":"https://github.com/suhaotian/async-plugins","commit_stats":null,"previous_names":["suhaotian/async-plugins"],"tags_count":3,"template":true,"template_full_name":null,"purl":"pkg:github/suhaotian/async-plugins","repository_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/suhaotian%2Fasync-plugins","tags_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/suhaotian%2Fasync-plugins/tags","releases_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/suhaotian%2Fasync-plugins/releases","manifests_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/suhaotian%2Fasync-plugins/manifests","owner_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners/suhaotian","download_url":"https://codeload.github.com/suhaotian/async-plugins/tar.gz/refs/heads/main","sbom_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/suhaotian%2Fasync-plugins/sbom","scorecard":null,"host":{"name":"GitHub","url":"https://github.com","kind":"github","repositories_count":286080680,"owners_count":29550815,"icon_url":"https://github.com/github.png","version":null,"created_at":"2022-05-30T11:31:42.601Z","updated_at":"2026-02-17T14:33:00.708Z","status":"ssl_error","status_checked_at":"2026-02-17T14:32:58.657Z","response_time":100,"last_error":"SSL_connect returned=1 errno=0 peeraddr=140.82.121.5:443 state=error: unexpected eof while reading","robots_txt_status":"success","robots_txt_updated_at":"2025-07-24T06:49:26.215Z","robots_txt_url":"https://github.com/robots.txt","online":false,"can_crawl_api":true,"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-cache","async-dedupe","async-poll","async-queue","async-retry"],"created_at":"2025-04-12T22:02:53.777Z","updated_at":"2026-02-17T17:01:42.185Z","avatar_url":"https://github.com/suhaotian.png","language":"TypeScript","funding_links":[],"categories":[],"sub_categories":[],"readme":"[![Build](https://github.com/suhaotian/async-plugins/actions/workflows/check.yml/badge.svg)](https://github.com/suhaotian/async-plugins/actions/workflows/check.yml)\n[![Size](https://deno.bundlejs.com/badge?q=async-plugins\u0026badge=detailed)](https://bundlejs.com/?q=async-plugins)\n[![Treeshaking](https://badgen.net/bundlephobia/tree-shaking/async-plugins)](https://bundlephobia.com/package/async-plugins@latest)\n[![jsDocs.io](https://img.shields.io/badge/jsDocs.io-reference-blue)](https://www.jsdocs.io/package/async-plugins)\n[![npm version](https://badgen.net/npm/v/async-plugins?color=green)](https://www.npmjs.com/package/async-plugins)\n![Downloads](https://img.shields.io/npm/dm/async-plugins.svg?style=flat)\n![typescript](https://badgen.net/badge/icon/typescript?icon=typescript\u0026label\u0026color=blue)\n\n## Intro\n\n🛠 A lightweight collection of TypeScript utilities for common async operation patterns. Each utility is optimized for performance and provides a clean, type-safe API.\n\n**Features:**\n\n- ⚡️ **async-retry**: Smart retry logic with exponential backoff for API calls and network operations\n- 🗄️ **async-cache**: Fast LRU caching with TTL support for expensive operations\n- 🎯 **async-dedupe**: Prevent duplicate API calls and redundant operations\n- 📊 **async-queue**: Control concurrency and resource usage with priority queues\n- 🔄 **async-poll**: Reliable polling with configurable intervals and backoff\n- 👊 Fully typed with TypeScript\n- 🎭 Comprehensive test coverage\n- 📦 Tree-shakeable and lightweight\n- 🚫 Zero dependencies (except tiny-lru)\n\n## Getting Started\n\n### Installing\n\n```sh\nnpm install async-plugins   # npm\npnpm add async-plugins     # pnpm\nbun add async-plugins      # bun\nyarn add async-plugins     # yarn\n```\n\n## Usage Examples\n\nExample online: https://stackblitz.com/edit/js-fjsqnhfc?file=index.js\n\n### Retry\n\nPerfect for handling flaky API calls or network operations:\n\n```ts\nimport { createAsyncRetry } from 'async-plugins';\n\nconst fetchWithRetry = createAsyncRetry({\n  retries: 3, // Try up to 3 times\n  minTimeout: 1000, // Start with 1s delay\n  maxTimeout: 10000, // Cap at 10s delay\n  factor: 2, // Double the delay each time\n  jitter: true, // Add randomness to prevent thundering herd\n  shouldRetry: (error) =\u003e {\n    // Only retry on network/5xx errors\n    return error.name === 'NetworkError' || (error.status \u0026\u0026 error.status \u003e= 500);\n  },\n  onRetry: (error, attempt) =\u003e {\n    console.warn(`Retry attempt ${attempt} after error:`, error);\n  },\n});\n\n// Example: Fetch user data with retries\nconst getUserData = async (userId: string) =\u003e {\n  try {\n    const response = await fetchWithRetry(() =\u003e\n      fetch(`/api/users/${userId}`).then((r) =\u003e r.json())\n    );\n    return response;\n  } catch (error) {\n    // All retries failed\n    console.error('Failed to fetch user data:', error);\n    throw error;\n  }\n};\n```\n\n### Cache\n\nOptimize expensive operations and API calls with smart caching:\n\n```ts\nimport { createAsyncCache } from 'async-plugins';\n\nconst cache = createAsyncCache({\n  ttl: 300000, // Cache for 5 minutes\n  maxSize: 1000, // Store up to 1000 items\n  staleWhileRevalidate: true, // Return stale data while refreshing\n});\n\n// Example: Cache expensive API calls\nconst getUserProfile = cache(\n  async (userId: string) =\u003e {\n    const response = await fetch(`/api/users/${userId}`);\n    return response.json();\n  },\n  // Optional: Custom cache key generator\n  (userId) =\u003e `user_profile:${userId}`\n);\n\n// First call fetches and caches\nconst profile1 = await getUserProfile('123');\n\n// Subsequent calls within TTL return cached data\nconst profile2 = await getUserProfile('123'); // instant return\n\n// After TTL expires, returns stale data and refreshes in background\nconst profile3 = await getUserProfile('123'); // instant return with stale data\n```\n\n### Dedupe\n\nPrevent duplicate API calls and redundant operations:\n\n```ts\nimport { createAsyncDedupe } from 'async-plugins';\n\nconst dedupe = createAsyncDedupe({\n  timeout: 5000, // Auto-expire after 5s\n  errorSharing: true, // Share errors between duplicate calls\n});\n\n// Example: Prevent duplicate API calls\nconst fetchUserData = dedupe(async (userId: string) =\u003e {\n  const response = await fetch(`/api/users/${userId}`);\n  return response.json();\n});\n\n// Multiple simultaneous calls with same ID\nconst [user1, user2] = await Promise.all([\n  fetchUserData('123'), // Makes API call\n  fetchUserData('123'), // Uses result from first call\n]);\n\n// Check if operation is in progress\nif (dedupe.isInProgress('123')) {\n  console.log('Fetch in progress...');\n}\n```\n\n### Queue\n\nControl concurrency and manage resource usage:\n\n```ts\nimport { createAsyncQueue } from 'async-plugins';\n\nconst queue = createAsyncQueue({\n  concurrency: 2, // Process 2 tasks at once\n  autoStart: true, // Start processing immediately\n});\n\n// Example: Rate-limit API calls\nconst processUsers = async (userIds: string[]) =\u003e {\n  const results = await queue.addAll(\n    userIds.map((id) =\u003e async () =\u003e {\n      const response = await fetch(`/api/users/${id}`);\n      return response.json();\n    })\n  );\n  return results;\n};\n\n// Monitor queue status\nqueue.onEmpty().then(() =\u003e {\n  console.log('Queue is empty');\n});\n\nqueue.onDrain().then(() =\u003e {\n  console.log('All tasks completed');\n});\n\n// Queue stats\nconsole.log(queue.stats());\n// { pending: 0, active: 2, completed: 10, errors: 0, total: 12 }\n```\n\n### Poll\n\nReliable polling with configurable intervals:\n\n```ts\nimport { createAsyncPoller } from 'async-plugins';\n\n// Example: Poll for job completion\nconst pollJobStatus = createAsyncPoller(\n  // Function to poll\n  async () =\u003e {\n    const response = await fetch('/api/job/123');\n    return response.json();\n  },\n  {\n    interval: 1000, // Poll every second\n    maxAttempts: 30, // Try up to 30 times\n    backoff: {\n      type: 'exponential',\n      factor: 2,\n      maxInterval: 30000,\n      jitter: true,\n    },\n    shouldContinue: (result) =\u003e result.status === 'running',\n    onProgress: (result) =\u003e {\n      console.log('Job progress:', result.progress);\n    },\n  }\n);\n\ntry {\n  const finalResult = await pollJobStatus.start();\n  console.log('Job completed:', finalResult);\n} catch (error) {\n  console.error('Polling failed:', error);\n}\n\n// Can stop polling manually if needed\npollJobStatus.stop();\n```\n\n## FAQ\n\n### 1. Why choose async-plugins?\n\n- 🎯 **Focused Purpose**: Each utility solves a specific async pattern problem\n- 📦 **Lightweight**: Minimal bundle size impact with tree-shaking support\n- 💪 **Type-Safe**: Written in TypeScript with comprehensive type definitions\n- 🔧 **Customizable**: Flexible configuration options for each utility\n- 🚀 **Production-Ready**: Well-tested and actively maintained\n\n### 2. Where can I get help?\n\nIf you have questions or run into issues:\n\n1. Check the [API Reference](https://www.jsdocs.io/package/async-plugins)\n2. Create an issue on [GitHub](https://github.com/suhaotian/async-plugins/issues)\n3. For security issues, please email me directly\n\n## Contributing\n\nContributions are welcome!\n\n## License\n\nMIT License. See [LICENSE](LICENSE) for details.\n\n## Star History\n\n[![Star History Chart](https://api.star-history.com/svg?repos=suhaotian/async-plugins\u0026type=Date)](https://star-history.com/#suhaotian/async-plugins\u0026Date)\n\n## Acknowledgements\n\nThis project wouldn't be possible without:\n\n- [tiny-lru](https://github.com/avoidwork/tiny-lru) for LRU cache implementation\n- Claude 3.7 Sonnet\n- Gemini 2.5 Pro\n","project_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fsuhaotian%2Fasync-plugins","html_url":"https://awesome.ecosyste.ms/projects/github.com%2Fsuhaotian%2Fasync-plugins","lists_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fsuhaotian%2Fasync-plugins/lists"}