{"id":30902772,"url":"https://github.com/kekyo/async-primitives","last_synced_at":"2026-03-06T09:02:39.546Z","repository":{"id":297418386,"uuid":"995907212","full_name":"kekyo/async-primitives","owner":"kekyo","description":"A collection of primitive functions for asynchronous operations in TypeScript/JavaScript.","archived":false,"fork":false,"pushed_at":"2025-09-04T03:08:41.000Z","size":306,"stargazers_count":2,"open_issues_count":0,"forks_count":0,"subscribers_count":0,"default_branch":"main","last_synced_at":"2025-09-04T03:26:37.084Z","etag":null,"topics":["async","asynclock","deferred","delay","mutex","promise","reader-writer-lock","semaphore","synchronization-primitives","typescript"],"latest_commit_sha":null,"homepage":"","language":"TypeScript","has_issues":false,"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/kekyo.png","metadata":{"files":{"readme":"README.md","changelog":null,"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,"notice":null,"maintainers":null,"copyright":null,"agents":null,"dco":null,"cla":null}},"created_at":"2025-06-04T07:18:06.000Z","updated_at":"2025-09-04T02:27:13.000Z","dependencies_parsed_at":"2025-07-13T13:17:24.204Z","dependency_job_id":"2d096819-bc1b-4f01-b039-004673f39aa9","html_url":"https://github.com/kekyo/async-primitives","commit_stats":null,"previous_names":["kekyo/async-primitives"],"tags_count":8,"template":false,"template_full_name":null,"purl":"pkg:github/kekyo/async-primitives","repository_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/kekyo%2Fasync-primitives","tags_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/kekyo%2Fasync-primitives/tags","releases_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/kekyo%2Fasync-primitives/releases","manifests_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/kekyo%2Fasync-primitives/manifests","owner_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners/kekyo","download_url":"https://codeload.github.com/kekyo/async-primitives/tar.gz/refs/heads/main","sbom_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/kekyo%2Fasync-primitives/sbom","scorecard":null,"host":{"name":"GitHub","url":"https://github.com","kind":"github","repositories_count":274263497,"owners_count":25252078,"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","status":"online","status_checked_at":"2025-09-09T02:00:10.223Z","response_time":80,"last_error":null,"robots_txt_status":"success","robots_txt_updated_at":"2025-07-24T06:49:26.215Z","robots_txt_url":"https://github.com/robots.txt","online":true,"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","asynclock","deferred","delay","mutex","promise","reader-writer-lock","semaphore","synchronization-primitives","typescript"],"created_at":"2025-09-09T08:11:28.812Z","updated_at":"2026-03-06T09:02:39.529Z","avatar_url":"https://github.com/kekyo.png","language":"TypeScript","funding_links":[],"categories":[],"sub_categories":[],"readme":"# async-primitives\n\nA collection of primitive functions for asynchronous operations in TypeScript/JavaScript.\n\n[![Project Status: Active – The project has reached a stable, usable state and is being actively developed.](https://www.repostatus.org/badges/latest/active.svg)](https://www.repostatus.org/#active)\n[![License: MIT](https://img.shields.io/badge/License-MIT-yellow.svg)](https://opensource.org/licenses/MIT)\n[![npm version](https://img.shields.io/npm/v/async-primitives.svg)](https://www.npmjs.com/package/async-primitives)\n\n---\n\n## What is this?\n\nIf you are interested in performing additional calculations on `Promise\u003cT\u003e`, you may find this small library useful.\nMutex, producer-consumer separation (side-effect operation), signaling (flag control), logical context and more.\n\n- Works in both browser and Node.js environments (16 or later, tested only 22).\n- No external dependencies.\n\n| Function                      | Description                                               |\n| :---------------------------- | :-------------------------------------------------------- |\n| `delay()`                     | Promise-based delay function                              |\n| `defer()`                     | Schedule callback for next event loop                     |\n| `onAbort()`                   | Register safer abort signal hooks with cleanup            |\n| `createMutex()`               | Promise-based mutex lock for critical sections            |\n| `createSemaphore()`           | Promise-based semaphore for limiting concurrent access    |\n| `createReaderWriterLock()`    | Read-write lock for multiple readers/single writer        |\n| `createDeferred()`            | External control of Promise resolution/rejection          |\n| `createDeferredGenerator()`   | External control of async generator with queue management |\n| `createConditional()`         | Automatic conditional trigger (one-waiter per trigger)    |\n| `createManuallyConditional()` | Manual conditional control (raise/drop state)             |\n\nAdvanced features:\n\n| Function             | Description                                  |\n| :------------------- | :------------------------------------------- |\n| `createAsyncLocal()` | Asynchronous context storage                 |\n| `LogicalContext`     | Low-level async execution context management |\n\n- The implementations previously known symbol as `AsyncLock` and `Signal` have been changed to `Mutex` and `Conditional`.\n  Although these symbol names can still be used, please note that they are marked as deprecated.\n  They may be removed in future versions.\n\n## Installation\n\n```bash\nnpm install async-primitives\n```\n\n---\n\n## Usage\n\nEach functions are independent and does not require knowledge of each other's assumptions.\n\n### delay()\n\nProvides a delay that can be awaited with `Promise\u003cvoid\u003e`, with support for cancellation via `AbortSignal.`\n\n```typescript\nimport { delay } from 'async-primitives';\n\n// Use delay\nawait delay(1000); // Wait for 1 second\n```\n\n```typescript\n// With AbortSignal\nconst c = new AbortController();\nawait delay(1000, c.signal); // Wait for 1 second\n```\n\n### defer()\n\nSchedules a callback to be executed asynchronously on the next event loop iteration.\n\n```typescript\nimport { defer } from 'async-primitives';\n\n// Use defer (Schedule callback for next event loop)\ndefer(() =\u003e {\n  console.log('Executes asynchronously');\n});\n```\n\n### onAbort()\n\nRegisters a hook function to `AbortSignal` abort events, enabling cleanup processing. Also supports early release.\n\n```typescript\nimport { onAbort } from 'async-primitives';\n\n// Use onAbort (Abort signal hooking)\nconst controller = new AbortController();\n\nconst releaseHandle = onAbort(controller.signal, (error: Error) =\u003e {\n  console.log('Operation was aborted!');\n  // (Will automatically cleanup when exit)\n});\n\n// (Cleanup early if needed)\nreleaseHandle.release();\n```\n\n### createMutex()\n\nProvides `Promise` based mutex functionality to implement critical sections that prevent race conditions in asynchronous operations.\n\n```typescript\nimport { createMutex } from 'async-primitives';\n\n// Use Mutex\nconst locker = createMutex();\n\n// Lock Mutex\nconst handler = await locker.lock();\ntry {\n  // Critical section, avoid race condition.\n} finally {\n  // Release Mutex\n  handler.release();\n}\n```\n\n```typescript\n// With AbortSignal\nconst handler = await locker.lock(c.signal);\n```\n\n### createDeferred()\n\nCreates a `Deferred\u003cT\u003e` object that allows external control of `Promise\u003cT\u003e` resolution or rejection.\nUseful for separating producers and consumers in asynchronous processing.\n\n```typescript\nimport { createDeferred } from 'async-primitives';\n\n// Use Deferred\nconst deferred = createDeferred\u003cnumber\u003e();\n\ndeferred.resolve(123); // (Produce result value)\ndeferred.reject(new Error()); // (Produce an error)\n\n// (Consumer)\nconst value = await deferred.promise;\n```\n\n```typescript\n// With AbortSignal support\nconst controller = new AbortController();\nconst abortableDeferred = createDeferred\u003cnumber\u003e(controller.signal);\n```\n\n### createDeferredGenerator()\n\nCreates a `DeferredGenerator\u003cT\u003e` object that allows external control of async generator `AsyncGenerator\u003cT, ...\u003e` yielding, returning and throwing operations.\nUseful for separating producers and consumers in streaming data patterns.\n\n```typescript\nimport { createDeferredGenerator } from 'async-primitives';\n\n// Basic usage - streaming data\nconst deferredGen = createDeferredGenerator\u003cstring\u003e();\n\n// Consumer - iterate over values as they arrive\nconst consumer = async () =\u003e {\n  for await (const value of deferredGen.generator) {\n    console.log('Received:', value);\n  }\n  console.log('Stream completed');\n};\n\n// Start consuming\nconsumer();\n\n// Producer - send values externally (now returns Promise\u003cvoid\u003e)\nawait deferredGen.yield('First value');\nawait deferredGen.yield('Second value');\nawait deferredGen.yield('Third value');\nawait deferredGen.return(); // Complete the stream\n```\n\nCan insert an error when yielding:\n\n```typescript\n// With error handling\nconst errorGen = createDeferredGenerator\u003cnumber\u003e();\n\nconst errorConsumer = async () =\u003e {\n  try {\n    for await (const value of errorGen.generator) {\n      console.log('Number:', value);\n    }\n  } catch (error) {\n    console.log('Error occurred:', error.message);\n  }\n};\n\nerrorConsumer();\nawait errorGen.yield(1);\nawait errorGen.yield(2);\nawait errorGen.throw(new Error('Something went wrong'));\n```\n\n#### Queue Size Management\n\nControl the maximum number of items that can be queued using the `maxItemReserved` option:\n\n```typescript\n// Limit queue to 3 items maximum\nconst limitedGen = createDeferredGenerator\u003cstring\u003e({ maxItemReserved: 3 });\n\n// When queue is full, yield operations will wait for space\nawait limitedGen.yield('item1');\nawait limitedGen.yield('item2');\nawait limitedGen.yield('item3'); // Queue is now full\n\n// This will wait until consumer processes some items\nawait limitedGen.yield('item4'); // Waits for queue space\n```\n\n### createConditional()\n\nCreates an automatically or manually controlled signal that can be raise and drop.\nMultiple waiters can await for the same signal, and all will be resolved when the signal is raise.\n\nThe `Conditional` (automatic conditional) is \"trigger\" automatically raise-and-drop to release only one-waiter:\n\n```typescript\nimport { createConditional } from 'async-primitives';\n\n// Create an automatic conditional\nconst signal = createConditional();\n\n// Start multiple waiters\nconst waiter1 = signal.wait();\nconst waiter2 = signal.wait();\n\n// Trigger the signal - only one waiter will resolve per trigger\nsignal.trigger(); // waiter1 resolves\n\nawait waiter1;\nconsole.log('First waiter resolved');\n\n// Second waiter is still waiting\nsignal.trigger(); // waiter2 resolves\n\nawait waiter2;\nconsole.log('Second waiter resolved');\n```\n\n```typescript\n// Wait with AbortSignal support\nconst controller = new AbortController();\ntry {\n  const waitPromise = signal.wait(controller.signal);\n  // Abort the wait operation\n  controller.abort();\n  await waitPromise;\n} catch (error) {\n  console.log('Wait was aborted');\n}\n```\n\n### createManuallyConditional()\n\nThe `ManuallyConditional` is manually controlled raise and drop state, and trigger action is optional.\n\n```typescript\nimport { createManuallyConditional } from 'async-primitives';\n\n// Create a manually conditional\nconst signal = createManuallyConditional();\n\n// Start multiple waiters\nconst waiter1 = signal.wait();\nconst waiter2 = signal.wait();\n\n// Raise the signal - all waiters will resolve\nsignal.raise();\n\n// Or, you can release only one-waiter\n//signal.trigger();　　// waiter1 resolves\n\nawait Promise.all([waiter1, waiter2]);\nconsole.log('All waiters resolved');\n\n// Drop the signal\nsignal.drop();\n```\n\n```typescript\n// Wait with AbortSignal support\nconst controller = new AbortController();\ntry {\n  await signal.wait(controller.signal);\n} catch (error) {\n  console.log('Wait was aborted');\n}\n```\n\n### createSemaphore()\n\nCreates a `Semaphore` that limits the number of concurrent operations to a specified count.\nUseful for rate limiting, resource pooling, and controlling concurrent access to limited resources.\n\n```typescript\nimport { createSemaphore } from 'async-primitives';\n\n// Create a semaphore with max 3 concurrent operations\nconst semaphore = createSemaphore(3);\n\n// Acquire a resource\nconst handle = await semaphore.acquire();\ntry {\n  // Critical section - only 3 operations can run concurrently\n  await performExpensiveOperation();\n} finally {\n  // Release the resource\n  handle.release();\n}\n\n// Check available resources\nconsole.log(`Available: ${semaphore.availableCount}`);\nconsole.log(`Waiting: ${semaphore.pendingCount}`);\n```\n\nRate limiting example for API calls:\n\n```typescript\n// Limit to 5 concurrent API calls\nconst apiSemaphore = createSemaphore(5);\n\nconst rateLimitedFetch = async (url: string) =\u003e {\n  const handle = await apiSemaphore.acquire();\n  try {\n    return await fetch(url);\n  } finally {\n    handle.release();\n  }\n};\n\n// Process many URLs with controlled concurrency\nconst urls = ['url1', 'url2' /* ... many more ... */];\nconst promises = urls.map((url) =\u003e rateLimitedFetch(url));\nconst results = await Promise.all(promises);\n// Only 5 requests will be in-flight at any time\n```\n\n```typescript\n// With AbortSignal support\nconst controller = new AbortController();\ntry {\n  const handle = await semaphore.acquire(controller.signal);\n\n  // Use the resource\n  handle.release();\n} catch (error) {\n  console.log('Semaphore acquisition was aborted');\n}\n```\n\n### createReaderWriterLock()\n\nCreates a `ReaderWriterLock` that allows multiple concurrent readers but only one exclusive writer.\n\nLock policies:\n\n- `write-preferring` (default): When a writer is waiting, new readers must wait until the writer completes\n- `read-preferring`: New readers can acquire the lock even when writers are waiting\n\n```typescript\nimport { createReaderWriterLock } from 'async-primitives';\n\n// Create a reader-writer lock (default: write-preferring)\nconst rwLock = createReaderWriterLock();\n\n// With specific policy\nconst readPreferringLock = createReaderWriterLock({\n  policy: 'read-preferring',\n});\n\n// Backward compatible with legacy API\nconst rwLockLegacy = createReaderWriterLock(10); // maxConsecutiveCalls\n\n// Multiple readers can access concurrently\nconst readData = async () =\u003e {\n  const handle = await rwLock.readLock();\n  try {\n    // Multiple threads can read simultaneously\n    const data = await readFromSharedResource();\n    return data;\n  } finally {\n    handle.release();\n  }\n};\n\n// Writers have exclusive access\nconst writeData = async (newData: any) =\u003e {\n  const handle = await rwLock.writeLock();\n  try {\n    // Exclusive access - no readers or other writers\n    await writeToSharedResource(newData);\n  } finally {\n    handle.release();\n  }\n};\n\n// Check lock state\nconsole.log(`Current readers: ${rwLock.currentReaders}`);\nconsole.log(`Has writer: ${rwLock.hasWriter}`);\nconsole.log(`Pending readers: ${rwLock.pendingReadersCount}`);\nconsole.log(`Pending writers: ${rwLock.pendingWritersCount}`);\n```\n\nCache implementation example:\n\n```typescript\nconst cacheLock = createReaderWriterLock();\nconst cache = new Map();\n\n// Read from cache (multiple concurrent reads allowed)\nconst getCached = async (key: string) =\u003e {\n  const handle = await cacheLock.readLock();\n  try {\n    return cache.get(key);\n  } finally {\n    handle.release();\n  }\n};\n\n// Update cache (exclusive write access)\nconst updateCache = async (key: string, value: any) =\u003e {\n  const handle = await cacheLock.writeLock();\n  try {\n    cache.set(key, value);\n  } finally {\n    handle.release();\n  }\n};\n\n// Clear cache (exclusive write access)\nconst clearCache = async () =\u003e {\n  const handle = await cacheLock.writeLock();\n  try {\n    cache.clear();\n  } finally {\n    handle.release();\n  }\n};\n```\n\n```typescript\n// With AbortSignal support\nconst controller = new AbortController();\ntry {\n  const readHandle = await rwLock.readLock(controller.signal);\n\n  // Read operations...\n  readHandle.release();\n} catch (error) {\n  console.log('Lock acquisition was aborted');\n}\n```\n\n### ES2022+ using statement\n\nUse with using statement (requires ES2022+ or equivalent polyfill)\n\n```typescript\nconst locker = createMutex();\n\n{\n  using handler = await locker.lock();\n\n  // (Auto release when exit the scope.)\n}\n\n{\n  using handle = onAbort(controller.signal, () =\u003e {\n    console.log('Cleanup on aborts');\n  });\n\n  // (Auto release when exit the scope.)\n}\n\n// Semaphore with using statement\nconst semaphore = createSemaphore(3);\n\n{\n  using handle = await semaphore.acquire();\n\n  // Perform rate-limited operation\n  await performOperation();\n\n  // (Auto release when exit the scope.)\n}\n\n// ReaderWriterLock with using statement\nconst rwLock = createReaderWriterLock();\n\n{\n  // Reader scope\n  using readHandle = await rwLock.readLock();\n\n  const data = await readSharedData();\n\n  // (Auto release when exit the scope.)\n}\n\n{\n  // Writer scope\n  using writeHandle = await rwLock.writeLock();\n\n  await writeSharedData(newData);\n\n  // (Auto release when exit the scope.)\n}\n```\n\n## Advanced Topic\n\n### createAsyncLocal()\n\nProvides asynchronous context storage similar to thread-local storage, but separated by asynchronous context instead of threads.\nValues are maintained across asynchronous boundaries like `setTimeout`, `await`, and `Promise` chains within the same logical context.\n\n```typescript\nimport { createAsyncLocal } from 'async-primitives';\n\n// Create an AsyncLocal instance\nconst asyncLocal = createAsyncLocal\u003cstring\u003e();\n\n// Set a value in the current context\nasyncLocal.setValue('context value');\n\n// Value is maintained across setTimeout\nsetTimeout(() =\u003e {\n  console.log(asyncLocal.getValue()); // 'context value'\n}, 100);\n\n// Value is maintained across await boundaries\nconst example = async () =\u003e {\n  asyncLocal.setValue('before await');\n\n  await delay(100);\n\n  console.log(asyncLocal.getValue()); // 'before await'\n};\n\n// Value is maintained in Promise chains\nPromise.resolve()\n  .then(() =\u003e {\n    asyncLocal.setValue('in promise');\n    return asyncLocal.getValue();\n  })\n  .then((value) =\u003e {\n    console.log(value); // 'in promise'\n  });\n```\n\nNOTE: The above example is no different than using a variable in the global scope.\nIn fact, to isolate the \"asynchronous context\" and observe different results, you must use `LogicalContext` below section.\n\n### LogicalContext Operations\n\n`LogicalContext` provides low-level APIs for managing asynchronous execution contexts.\nThese are automatically used by `createAsyncLocal()` but can also be used directly for advanced scenarios.\n\n```typescript\nimport {\n  setLogicalContextValue,\n  getLogicalContextValue,\n  runOnNewLogicalContext,\n  getCurrentLogicalContextId,\n} from 'async-primitives';\n\n// Direct context value manipulation\nconst key = Symbol('my-context-key');\nsetLogicalContextValue(key, 'some value');\nconst value = getLogicalContextValue\u003cstring\u003e(key); // 'some value'\n\n// Get current context ID\nconst contextId = getCurrentLogicalContextId();\nconsole.log(`Current context: ${contextId.toString()}`);\n\n// Execute code in a new isolated context\nconst result = runOnNewLogicalContext('my-operation', () =\u003e {\n  // This runs in a completely new context\n  const isolatedValue = getLogicalContextValue\u003cstring\u003e(key); // undefined\n\n  setLogicalContextValue(key, 'isolated value');\n  return getLogicalContextValue\u003cstring\u003e(key); // 'isolated value'\n});\n\n// Back to original context\nconst originalValue = getLogicalContextValue\u003cstring\u003e(key); // 'some value'\n```\n\nWhen using `LogicalContext` for the first time, hooks are inserted into various runtime functions and definitions in JavaScript to maintain the context correctly. Note that these create some overhead.\n\n| Target                         | Purpose                                                         |\n| :----------------------------- | :-------------------------------------------------------------- |\n| `setTimeout`                   | Maintains context across timer callbacks                        |\n| `setInterval`                  | Maintains context across interval callbacks                     |\n| `queueMicrotask`               | Preserves context in microtask queue                            |\n| `setImmediate`                 | Preserves context in immediate queue (Node.js only)             |\n| `process.nextTick`             | Preserves context in next tick queue (Node.js only)             |\n| `Promise`                      | Captures context for `then()`, `catch()` and `finally()` chains |\n| `EventTarget.addEventListener` | Maintains context in all EventTarget event handlers             |\n| `Element.addEventListener`     | Maintains context in DOM event handlers                         |\n| `requestAnimationFrame`        | Preserves context in animation callbacks                        |\n| `XMLHttpRequest`               | Maintains context in XHR event handlers and callbacks           |\n| `WebSocket`                    | Maintains context in WebSocket event handlers and callbacks     |\n| `MutationObserver`             | Preserves context in DOM mutation observer callbacks            |\n| `ResizeObserver`               | Preserves context in element resize observer callbacks          |\n| `IntersectionObserver`         | Preserves context in intersection observer callbacks            |\n| `Worker`                       | Maintains context in Web Worker event handlers                  |\n| `MessagePort`                  | Maintains context in MessagePort communication handlers         |\n\nNOTE: `LogicalContext` values are isolated between different contexts but maintained across asynchronous boundaries within the same context.\nThis enables proper context isolation in complex asynchronous applications.\n\n### createMutex() Parameter Details\n\nIn `createMutex(maxConsecutiveCalls?: number)`, you can specify the `maxConsecutiveCalls` parameter (default value: 20).\n\nThis value sets the limit for consecutive executions when processing the lock's waiting queue:\n\n- **Small values (e.g., 1-5)**\n  - Returns control to the event loop more frequently\n  - Minimizes impact on other asynchronous operations\n  - May slightly reduce lock processing throughput\n\n- **Large values (e.g., 50-100)**\n  - Executes more lock processes consecutively\n  - Improves lock processing throughput\n  - May block other asynchronous operations for longer periods\n\n- **Recommended settings**\n  - Default value (20) is suitable for most use cases\n  - For UI responsiveness priority: lower values (3-7)\n  - For high throughput needs like batch processing: higher values (20-100)\n\n```typescript\n// Prioritize UI responsiveness\nconst uiLocker = createMutex(5);\n\n// High throughput processing\nconst batchLocker = createMutex(50);\n```\n\n---\n\n## Benchmark results\n\nThese results do not introduce hooks by `LogicalContext`. See [benchmark/suites/](benchmark/suites/).\n\n| Benchmark                                                                | Operations/sec | Avg Time (ms) | Median Time (ms) | Std Dev (ms) | Total Time (ms) |\n| ------------------------------------------------------------------------ | -------------- | ------------- | ---------------- | ------------ | --------------- |\n| delay(0)                                                                 | 934            | 1079.586      | 1070.068         | 126.534      | 1000.78         |\n| delay(1)                                                                 | 934            | 1071.761      | 1068.625         | 49.364       | 1001.03         |\n| Mutex acquire/release                                                    | 274,303        | 4.474         | 3.576            | 38.848       | 1000            |\n| Semaphore(1) acquire/release                                             | 290,067        | 4.331         | 3.357            | 41.765       | 1000            |\n| Semaphore(2) acquire/release                                             | 288,334        | 4.435         | 3.396            | 43.934       | 1000            |\n| Semaphore(5) acquire/release                                             | 290,287        | 4.419         | 3.366            | 44.587       | 1000            |\n| Semaphore(10) acquire/release                                            | 292,229        | 4.342         | 3.356            | 43.939       | 1000            |\n| Semaphore(1) sequential (100x)                                           | 10,024         | 111.958       | 97.473           | 142.563      | 1000.01         |\n| Semaphore(5) sequential (100x)                                           | 10,024         | 113.495       | 96.992           | 155.519      | 1000            |\n| Semaphore(1) concurrent (10x)                                            | 63,539         | 18.449        | 15.229           | 59.406       | 1000.01         |\n| Semaphore(2) concurrent (10x)                                            | 64,914         | 17.86         | 15.108           | 59.934       | 1000.01         |\n| Semaphore(5) concurrent (10x)                                            | 66,078         | 17.944        | 14.677           | 66.436       | 1000.01         |\n| Semaphore(2) high contention (20x)                                       | 34,480         | 33.178        | 28.453           | 73.498       | 1000.01         |\n| Semaphore(5) high contention (50x)                                       | 14,610         | 80.79         | 66.414           | 259.057      | 1000.02         |\n| Semaphore(5) maxCalls=10 sequential (100x)                               | 9,970          | 114.741       | 97.973           | 164.314      | 1000.08         |\n| Semaphore(5) maxCalls=50 sequential (100x)                               | 10,013         | 113.082       | 97.503           | 150.644      | 1000.1          |\n| Semaphore(5) maxCalls=100 sequential (100x)                              | 9,722          | 126.805       | 98.725           | 602.04       | 1000.24         |\n| ReaderWriterLock readLock acquire/release (write-preferring)             | 208,591        | 6.592         | 4.699            | 78.696       | 1000            |\n| ReaderWriterLock writeLock acquire/release (write-preferring)            | 207,769        | 7             | 4.669            | 207.649      | 1000            |\n| ReaderWriterLock readLock acquire/release (read-preferring)              | 210,419        | 7.163         | 4.659            | 220.061      | 1014.02         |\n| ReaderWriterLock writeLock acquire/release (read-preferring)             | 211,489        | 6.471         | 4.659            | 74.975       | 1000            |\n| ReaderWriterLock sequential reads (100x, write-preferring)               | 9,826          | 118.356       | 99.567           | 196.413      | 1000.94         |\n| ReaderWriterLock sequential writes (100x, write-preferring)              | 9,761          | 132.774       | 99.136           | 991.938      | 1000.05         |\n| ReaderWriterLock sequential reads (100x, read-preferring)                | 9,902          | 117.885       | 99.066           | 200.593      | 1001.19         |\n| ReaderWriterLock sequential writes (100x, read-preferring)               | 9,807          | 117.465       | 99.937           | 184.883      | 1000.1          |\n| ReaderWriterLock concurrent readers (10x, write-preferring)              | 61,960         | 19.46         | 15.849           | 79.852       | 1000.42         |\n| ReaderWriterLock concurrent readers (20x, write-preferring)              | 36,276         | 32.669        | 26.88            | 105.354      | 1000.02         |\n| ReaderWriterLock concurrent readers (10x, read-preferring)               | 59,870         | 22.843        | 15.769           | 335.036      | 1000.01         |\n| ReaderWriterLock concurrent readers (20x, read-preferring)               | 36,144         | 32.936        | 27.081           | 100.923      | 1001.41         |\n| ReaderWriterLock read-heavy (100 ops, write-preferring)                  | 7,975          | 144.183       | 121.207          | 158.526      | 1000.05         |\n| ReaderWriterLock read-heavy (100 ops, read-preferring)                   | 8,057          | 141.475       | 120.446          | 150.893      | 1000.09         |\n| ReaderWriterLock write-heavy (100 ops, write-preferring)                 | 7,065          | 174.014       | 136.876          | 814.345      | 1000.06         |\n| ReaderWriterLock write-heavy (100 ops, read-preferring)                  | 7,050          | 163.203       | 136.747          | 168.259      | 1000.11         |\n| ReaderWriterLock balanced (100 ops, write-preferring)                    | 7,449          | 155.188       | 129.793          | 168.603      | 1000.03         |\n| ReaderWriterLock balanced (100 ops, read-preferring)                     | 7,554          | 158.157       | 127.669          | 207.97       | 1000.03         |\n| ReaderWriterLock maxCalls=10 mixed (100 ops, write-preferring)           | 7,572          | 158.004       | 127.098          | 208.476      | 1000.16         |\n| ReaderWriterLock maxCalls=50 mixed (100 ops, write-preferring)           | 8,199          | 139.732       | 117.791          | 177.521      | 1000.62         |\n| ReaderWriterLock maxCalls=10 mixed (100 ops, read-preferring)            | 7,814          | 142.376       | 123.602          | 119.782      | 1000.05         |\n| ReaderWriterLock maxCalls=50 mixed (100 ops, read-preferring)            | 8,270          | 134.827       | 117.15           | 126.091      | 1000.01         |\n| ReaderWriterLock write-preference test (50 ops)                          | 15,298         | 73.611        | 63.599           | 89.2         | 1000            |\n| ReaderWriterLock read-preference test (50 ops)                           | 14,740         | 76.531        | 65.864           | 88.866       | 1000.03         |\n| Deferred resolve                                                         | 967,076        | 1.079         | 1.022            | 2.304        | 1000            |\n| Deferred reject/catch                                                    | 161,211        | 6.457         | 6.131            | 36.68        | 1000            |\n| defer callback                                                           | 634,484        | 1.651         | 1.553            | 8.61         | 1000            |\n| defer [setTimeout(0)]                                                    | 942            | 1099.454      | 1067.532         | 190.464      | 1001.6          |\n| onAbort setup/cleanup                                                    | 734,009        | 1.42          | 1.353            | 12.645       | 1000            |\n| Mutex Sequential (1000x) - maxCalls: 1                                   | 773            | 1439.014      | 1186.095         | 650.3        | 1000.11         |\n| Mutex Sequential (1000x) - maxCalls: 5                                   | 798            | 1342.9        | 1167.278         | 474.905      | 1000.46         |\n| Mutex Sequential (1000x) - maxCalls: 10                                  | 786            | 1398.933      | 1165.956         | 616.517      | 1000.24         |\n| Mutex Sequential (1000x) - maxCalls: 20                                  | 806            | 1316.3        | 1162.615         | 420.848      | 1000.39         |\n| Mutex Sequential (1000x) - maxCalls: 50                                  | 803            | 1330.459      | 1162.12          | 455.191      | 1000.51         |\n| Mutex Sequential (1000x) - maxCalls: 100                                 | 804            | 1328.995      | 1161.147         | 452.855      | 1000.73         |\n| Mutex Sequential (1000x) - maxCalls: 1000                                | 806            | 1323.39       | 1160.656         | 449.744      | 1000.48         |\n| Mutex High-freq (500x) - maxCalls: 1                                     | 1,561          | 796.818       | 599.555          | 700.583      | 1000.01         |\n| Mutex High-freq (500x) - maxCalls: 5                                     | 1,609          | 726.243       | 589.465          | 529.868      | 1000.04         |\n| Mutex High-freq (500x) - maxCalls: 10                                    | 1,631          | 696.869       | 586.359          | 447.914      | 1000.01         |\n| Mutex High-freq (500x) - maxCalls: 20                                    | 1,636          | 689.876       | 585.081          | 428.906      | 1000.32         |\n| Mutex High-freq (500x) - maxCalls: 50                                    | 1,637          | 681.668       | 584.35           | 391.355      | 1001.37         |\n| Mutex High-freq (500x) - maxCalls: 100                                   | 1,637          | 678.442       | 584.475          | 379.747      | 1000.02         |\n| Mutex High-freq (500x) - maxCalls: 1000                                  | 1,625          | 719.7         | 584.675          | 537.45       | 1000.38         |\n| Mutex Concurrent (20x) - maxCalls: 1                                     | 17,365         | 73.686        | 56.175           | 829.884      | 1000.06         |\n| Mutex Concurrent (20x) - maxCalls: 5                                     | 29,719         | 39.954        | 33.052           | 106.234      | 1000.02         |\n| Mutex Concurrent (20x) - maxCalls: 10                                    | 33,241         | 36.628        | 29.485           | 110.818      | 1000.02         |\n| Mutex Concurrent (20x) - maxCalls: 20                                    | 36,190         | 33.319        | 26.981           | 113.952      | 1000.01         |\n| Mutex Concurrent (20x) - maxCalls: 50                                    | 36,541         | 31.079        | 26.92            | 69.48        | 1000            |\n| Mutex Concurrent (20x) - maxCalls: 100                                   | 36,529         | 31.222        | 26.921           | 73.062       | 1000.01         |\n| Mutex Concurrent (20x) - maxCalls: 1000                                  | 35,973         | 32.576        | 26.961           | 83.952       | 1000.03         |\n| Mutex Ultra-high-freq (2000x) - maxCalls: 1                              | 370            | 2900.41       | 2380.239         | 884.207      | 1000.64         |\n| Mutex Ultra-high-freq (2000x) - maxCalls: 5                              | 388            | 2692.99       | 2342.889         | 631.597      | 1001.79         |\n| Mutex Ultra-high-freq (2000x) - maxCalls: 10                             | 390            | 2672.744      | 2335.075         | 607.606      | 1002.28         |\n| Mutex Ultra-high-freq (2000x) - maxCalls: 20                             | 379            | 2907.13       | 2336.011         | 1715.718     | 1000.05         |\n| Mutex Ultra-high-freq (2000x) - maxCalls: 50                             | 391            | 2674.667      | 2329.264         | 633.959      | 1000.33         |\n| Mutex Ultra-high-freq (2000x) - maxCalls: 100                            | 393            | 2640.003      | 2327.25          | 572.272      | 1003.2          |\n| Mutex Ultra-high-freq (2000x) - maxCalls: 1000                           | 391            | 2669.926      | 2326.478         | 625.909      | 1001.22         |\n| Conditional trigger/wait                                                 | 508,413        | 2.119         | 1.944            | 8.544        | 1000.1          |\n| Conditional trigger reaction time                                        | 452,111        | 2.37          | 2.193            | 8.231        | 1000            |\n| Conditional multiple waiters with trigger                                | 84,426         | 12.058        | 11.692           | 10.464       | 1000.01         |\n| ManuallyConditional raise/wait                                           | 369,773        | 2.864         | 2.685            | 9.754        | 1000            |\n| ManuallyConditional raise reaction time                                  | 338,972        | 3.226         | 2.925            | 14.948       | 1000            |\n| ManuallyConditional trigger/wait                                         | 371,705        | 2.831         | 2.665            | 8.58         | 1000            |\n| ManuallyConditional trigger reaction time                                | 338,929        | 3.123         | 2.926            | 8.218        | 1000.08         |\n| ManuallyConditional multiple waiters with raise                          | 80,330         | 13.079        | 12.294           | 17.541       | 1000.01         |\n| ManuallyConditional multiple waiters with trigger                        | 79,769         | 13.118        | 12.384           | 14.868       | 1000.01         |\n| Conditional vs ManuallyConditional - single waiter (Conditional)         | 510,197        | 2.093         | 1.944            | 6.925        | 1000            |\n| Conditional vs ManuallyConditional - single waiter (ManuallyConditional) | 367,391        | 2.889         | 2.695            | 9.852        | 1000            |\n| Conditional vs ManuallyConditional - batch waiters (Conditional)         | 145,223        | 7.532         | 6.803            | 23.79        | 1000.01         |\n| Conditional vs ManuallyConditional - batch waiters (ManuallyConditional) | 133,227        | 8.217         | 7.414            | 24.952       | 1000.01         |\n| [Comparison] Mutex single acquire/release                                | 268,954        | 5.33          | 3.586            | 94.867       | 1000            |\n| [Comparison] Semaphore(1) single acquire/release                         | 292,941        | 4.69          | 3.356            | 63.966       | 1000            |\n| [Comparison] Mutex sequential (50x)                                      | 16,375         | 71.832        | 59.912           | 139.937      | 1000.05         |\n| [Comparison] Semaphore(1) sequential (50x)                               | 19,336         | 62.213        | 50.855           | 160.662      | 1000.01         |\n| [Comparison] RWLock write-only sequential (50x)                          | 18,838         | 65.947        | 52.048           | 203.229      | 1000.02         |\n| [Comparison] Mutex concurrent (20x)                                      | 32,657         | 41.34         | 28.324           | 172.457      | 1002.09         |\n| [Comparison] Semaphore(1) concurrent (20x)                               | 34,374         | 35.436        | 28.413           | 110.662      | 1000.01         |\n| [Comparison] RWLock write-only concurrent (20x)                          | 32,800         | 37.984        | 29.816           | 133.543      | 1000.54         |\n| [Comparison] Semaphore(5) for pool (20 requests)                         | 35,386         | 34.342        | 27.762           | 111.709      | 1000.02         |\n| [Comparison] 5 Mutexes round-robin (20 requests)                         | 25,174         | 49.959        | 38.913           | 156.849      | 1000.03         |\n| [Comparison] RWLock read-mostly (90% read)                               | 16,215         | 75.794        | 59.922           | 166.724      | 1000.03         |\n| [Comparison] Mutex for read-mostly (simulated)                           | 14,888         | 90.556        | 65.052           | 899.732      | 1000.01         |\n| [Scenario] Connection Pool - Semaphore(3)                                | 67,270         | 19.833        | 14.527           | 116.812      | 1000.01         |\n| [Scenario] Cache - RWLock (70% read, 30% write)                          | 24,514         | 53.69         | 39.358           | 186.92       | 1000.03         |\n| [Scenario] Critical Section - Mutex                                      | 46,239         | 28.861        | 20.989           | 139.625      | 1000.01         |\n| [HighContention] Mutex (50 concurrent)                                   | 14,348         | 88.91         | 67.156           | 220.185      | 1000.06         |\n| [HighContention] Semaphore(1) (50 concurrent)                            | 14,429         | 80.106        | 67.125           | 151.764      | 1000.04         |\n| [HighContention] Semaphore(10) (50 concurrent)                           | 15,763         | 71.495        | 62.006           | 102.435      | 1000.01         |\n| [HighContention] RWLock writes (50 concurrent)                           | 14,082         | 81.354        | 69.059           | 118.924      | 1000.01         |\n| [HighContention] RWLock reads (50 concurrent)                            | 17,010         | 67.396        | 57.608           | 121.204      | 1000.02         |\n\n**Test Environment:** Node.js v22.19.0, linux x64  \n**CPU:** AMD EPYC 7763 64-Core Processor  \n**Memory:** 16GB  \n**Last Updated:** 2025-09-04\n\n---\n\n## License\n\nUnder MIT.\n","project_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fkekyo%2Fasync-primitives","html_url":"https://awesome.ecosyste.ms/projects/github.com%2Fkekyo%2Fasync-primitives","lists_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fkekyo%2Fasync-primitives/lists"}