{"id":19592302,"url":"https://github.com/smikhalevski/parallel-universe","last_synced_at":"2025-04-27T14:33:40.476Z","repository":{"id":44262370,"uuid":"452379444","full_name":"smikhalevski/parallel-universe","owner":"smikhalevski","description":"🚀 The set of async flow control structures and promise utils.","archived":false,"fork":false,"pushed_at":"2025-04-04T22:53:26.000Z","size":4407,"stargazers_count":5,"open_issues_count":0,"forks_count":1,"subscribers_count":2,"default_branch":"master","last_synced_at":"2025-04-04T23:24:38.339Z","etag":null,"topics":["async","async-queue","blocker","concurrency","delay","executor","lock","parallel","pool","promise","queue","repeat-until","sleep","thread-pool","timeout"],"latest_commit_sha":null,"homepage":"https://smikhalevski.github.io/parallel-universe/","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/smikhalevski.png","metadata":{"files":{"readme":"README.md","changelog":null,"contributing":null,"funding":null,"license":"LICENSE.txt","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}},"created_at":"2022-01-26T17:47:49.000Z","updated_at":"2025-04-04T22:53:29.000Z","dependencies_parsed_at":"2023-11-16T00:09:39.293Z","dependency_job_id":"e6264769-4d75-40a9-bf7d-1c805c8cf641","html_url":"https://github.com/smikhalevski/parallel-universe","commit_stats":{"total_commits":40,"total_committers":1,"mean_commits":40.0,"dds":0.0,"last_synced_commit":"610059369dd6222750f6f987538ad214335de1ab"},"previous_names":[],"tags_count":15,"template":false,"template_full_name":null,"repository_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/smikhalevski%2Fparallel-universe","tags_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/smikhalevski%2Fparallel-universe/tags","releases_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/smikhalevski%2Fparallel-universe/releases","manifests_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/smikhalevski%2Fparallel-universe/manifests","owner_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners/smikhalevski","download_url":"https://codeload.github.com/smikhalevski/parallel-universe/tar.gz/refs/heads/master","host":{"name":"GitHub","url":"https://github.com","kind":"github","repositories_count":251154708,"owners_count":21544546,"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","async-queue","blocker","concurrency","delay","executor","lock","parallel","pool","promise","queue","repeat-until","sleep","thread-pool","timeout"],"created_at":"2024-11-11T08:34:34.301Z","updated_at":"2025-04-27T14:33:38.686Z","avatar_url":"https://github.com/smikhalevski.png","language":"TypeScript","funding_links":[],"categories":[],"sub_categories":[],"readme":"\u003cp align=\"center\"\u003e\n  \u003ca href=\"#readme\"\u003e\n    \u003cimg alt=\"Spaceman\" src=\"./logo.png\"/\u003e\n  \u003c/a\u003e\n\u003c/p\u003e\n\n```sh\nnpm install --save-prod parallel-universe\n```\n\n🚀 [API documentation is available here.](https://smikhalevski.github.io/parallel-universe/)\n\n- [`AbortablePromise`](#abortablepromise)\n- [`Deferred`](#deferred)\n- [`AsyncQueue`](#asyncqueue)\n- [`WorkPool`](#workpool)\n- [`Lock`](#lock)\n- [`Blocker`](#blocker)\n- [`PubSub`](#pubsub)\n- [`repeat`](#repeat)\n- [`retry`](#retry)\n- [`waitFor`](#waitfor)\n- [`delay`](#delay)\n- [`timeout`](#timeout)\n\n# `AbortablePromise`\n\nThe promise that can be aborted.\n\n```ts\nconst promise = new AbortablePromise((resolve, reject, signal) =\u003e {\n  signal.addEventListener('abort', () =\u003e {\n    // Listen to the signal being aborted\n  });\n  \n  // Resolve or reject the promise\n});\n\npromise.abort();\n```\n\nWhen [`abort`](https://smikhalevski.github.io/parallel-universe/classes/AbortablePromise.html#abort) is called,\nthe promise is instantly rejected with\nan [`AbortError`](https://developer.mozilla.org/en-US/docs/Web/API/DOMException#aborterror) if it isn't settled yet.\n\nProvide a custom abort reason:\n\n```ts\npromise.abort(new Error('Operation aborted'));\n```\n\nAbort promise if an external signal is aborted:\n\n```ts\npromise.withSignal(signal);\n```\n\n# `Deferred`\n\nThe promise that can be resolved externally.\n\n```ts\nconst promise = new Deferred\u003cstring\u003e();\n\npromise.then(value =\u003e {\n  doSomething(value);\n});\n\npromise.resolve('Mars');\n```\n\n# `AsyncQueue`\n\nAsynchronous queue decouples value providers and value consumers.\n\n```ts\nconst queue = new AsyncQueue();\n\n// Provider adds a value\nqueue.append('Mars');\n\n// Consumer takes a value\nqueue.take();\n// ⮕ AbortablePromise { 'Mars' }\n```\n\n`append` appends the value to the queue, while `take` removes the value from the queue as soon as it is available.\nIf there are no values in the queue upon `take` call then the returned promise is resolved after the next `append` call.\n\n```ts\nconst queue = new AsyncQueue();\n\n// The returned promise would be resolved after the append call\nqueue.take();\n// ⮕ AbortablePromise { 'Mars' }\n\nqueue.append('Mars');\n```\n\nConsumers receive values from the queue in the same order they were added by providers:\n\n```ts\nconst queue = new AsyncQueue();\n\nqueue.append('Mars');\nqueue.append('Venus');\n\nqueue.take();\n// ⮕ AbortablePromise { 'Mars' }\n\nqueue.take();\n// ⮕ AbortablePromise { 'Venus' }\n```\n\n## Acknowledgements\n\nIn some cases removing the value from the queue isn't the desirable behavior, since the consumer may not be able to\nprocess the taken value. Use `takeAck` to examine available value and acknowledge that it can be processed. `takeAck`\nreturns a tuple of the available value and the acknowledgement callback. The consumer should call `ack` to notify\nthe queue on weather to remove the value from the queue or to retain it.\n\n```ts\nqueue.takeAck().then(([value, ack]) =\u003e {\n  try {\n    if (doSomeChecks()) {\n      ack(true);\n      doSomething(value);\n    } \n  } finally {\n    ack(false);\n  }\n});\n```\n\nTo guarantee that consumers receive values in the same order as they were provided, acknowledgements prevent subsequent\nconsumers from being fulfilled until `ack` is called. Be sure to call `ack` to prevent the queue from being stuck\nindefinitely.\n\nCalling `ack` multiple times is safe, since only the first call would have an effect.\n\nTo acknowledge that the consumer can process the value, and the value must be removed from the queue use:\n\n```ts\nack(true);\n```\n\nTo acknowledge that the value should be retained by the queue use:\n\n```ts\nack(false);\n```\n\nThe value that was retained in the queue becomes available for the subsequent consumer.\n\n```ts\nconst queue = new AsyncQueue();\n\nqueue.append('Pluto');\n\nqueue.takeAck(([value, ack]) =\u003e {\n  ack(false); // Tells queue to retain the value\n});\n\nqueue.take();\n// ⮕ AbortablePromise { 'Pluto' }\n```\n\n# `WorkPool`\n\nThe callback execution pool that executes the limited number of callbacks in parallel while other submitted callbacks\nwait in the queue.\n\n```ts\n// The pool that processes 5 callbacks in parallel at maximum\nconst pool = new WorkPool(5);\n\npool.submit(signal =\u003e {\n  return Promise.resolve('Mars');\n});\n// ⮕ AbortablePromise\u003cstring\u003e\n```\n\nYou can change how many callbacks can the pool process in parallel:\n\n```ts\npool.setSize(2);\n// ⮕ Promise\u003cvoid\u003e\n```\n\n`setSize` returns the promise that is resolved when there are no excessive callbacks being processed in parallel.\n\nIf you resize the pool down, callbacks that are pending and exceed the new size limit, are notified via `signal` that\nthey must be aborted.\n\nTo abort all callbacks that are being processed by the pool and wait for their completion use:\n\n```ts\n// Resolved when all pending callbacks are fulfilled\npool.setSize(0);\n// ⮕ Promise\u003cvoid\u003e\n```\n\n# `Lock`\n\nPromise-based [lock implementation](https://en.wikipedia.org/wiki/Lock_(computer_science)).\n\nWhen someone tries to acquire a `Lock` they receive a promise for a release callback that is resolved as soon as\nprevious lock owner invokes their release callback.\n\n```ts\nconst lock = new Lock();\n\nlock.acquire();\n// ⮕ Promise\u003c() =\u003e void\u003e\n```\n\nYou can check that the lock is [locked](https://smikhalevski.github.io/parallel-universe/classes/Lock.html#isLocked)\nbefore acquiring a lock.\n\nFor example, if you want to force an async callback executions to be sequential you can use an external lock:\n\n```ts\nconst lock = new Lock();\n\nasync function doSomething() {\n  const release = await lock.acquire();\n  try {\n    // Long process is handled here\n  } finally {\n    release();\n  }\n}\n\n// Long process would be executed three times sequentially\ndoSomething();\ndoSomething();\ndoSomething();\n```\n\n# `Blocker`\n\nProvides a mechanism for blocking an async process and unblocking it from the outside.\n\n```ts\nconst blocker = new Blocker\u003cstring\u003e();\n\nblocker.block();\n// ⮕ Promise\u003cstring\u003e\n```\n\nYou can later unblock it passing a value that would fulfill the promise returned from the `block` call:\n\n```ts\nblocker.unblock('Mars');\n```\n\n# `PubSub`\n\nPublish–subscribe pattern implementation:\n\n```ts\nconst pubSub = new PubSub\u003cstring\u003e();\n\npubSub.subscribe(message =\u003e {\n  // Process the message\n});\n\npubSub.publish('Pluto');\n```\n\n# `repeat`\n\nInvokes a callback periodically with the given delay between settlements of returned promises until the condition is\nmet. By default, the callback is invoked indefinitely with no delay between settlements: \n\n```ts\nrepeat(async () =\u003e {\n  await doSomething();\n});\n// ⮕ AbortablePromise\u003cvoid\u003e\n```\n\nSpecify a delay between invocations:\n\n```ts\nrepeat(doSomething, 3000);\n// ⮕ AbortablePromise\u003cvoid\u003e\n```\n\nAbort the loop:\n\n```ts\nconst promise = repeat(doSomething, 3000);\n\npromise.abort();\n```\n\nSpecify the condition when the loop must be stopped. The example below randomly picks a planet name once every 3 seconds\nand fulfills the returned promise when `'Mars'` is picked: \n\n```ts\nrepeat(\n  () =\u003e ['Mars', 'Pluto', 'Venus'][Math.floor(Math.random() * 3)],\n  3000,\n  value =\u003e value === 'Mars'\n);\n// ⮕ AbortablePromise\u003cstring\u003e\n```\n\nYou can combine `repeat` with [`timeout`](#timeout) to limit the repeat duration:\n\n```ts\ntimeout(\n  repeat(async () =\u003e {\n    await doSomething();\n  }),\n  5000\n);\n```\n\n# `retry`\n\nInvokes a callback periodically until it successfully returns the result. If a callback throws an error or returns\na promise that is rejected then it is invoked again after a delay. \n\n```ts\nretry(async () =\u003e {\n  await doSomethingOrThrow();\n});\n// ⮕ AbortablePromise\u003cvoid\u003e\n```\n\nSpecify a delay between tries:\n\n```ts\nretry(doSomethingOrThrow, 3000);\n// ⮕ AbortablePromise\u003cvoid\u003e\n```\n\nSpecify maximum number of tries:\n\n```ts\nretry(doSomethingOrThrow, 3000, 5);\n// ⮕ AbortablePromise\u003cvoid\u003e\n```\n\nAbort the retry prematurely:\n\n```ts\nconst promise = retry(doSomethingOrThrow, 3000);\n\npromise.abort();\n```\n\nYou can combine `retry` with [`timeout`](#timeout) to limit the retry duration:\n\n```ts\ntimeout(\n  retry(async () =\u003e {\n    await doSomethingOrThrow();\n  }),\n  5000\n);\n```\n\n# `waitFor`\n\nReturns a promise that is fulfilled when a callback returns a truthy value:\n\n```ts\nwaitFor(async () =\u003e doSomething());\n// ⮕ AbortablePromise\u003cReturnType\u003ctypeof doSomething\u003e\u003e\n```\n\nIf you don't want `waitFor` to invoke the callback too frequently, provide a delay in milliseconds:\n\n```ts\nwaitFor(doSomething, 1_000);\n```\n\n# `delay`\n\nReturns a promise that resolves after a timeout. If signal is aborted then the returned promise is rejected with an\nerror.\n\n```ts\ndelay(100);\n// ⮕ AbortablePromise\u003cvoid\u003e\n```\n\nDelay can be resolved with a value:\n\n```ts\ndelay(100, 'Pluto');\n// ⮕ AbortablePromise\u003cstring\u003e\n```\n\n# `timeout`\n\nRejects with an error if the execution time exceeds the timeout.\n\n```ts\ntimeout(async signal =\u003e doSomething(), 100);\n// ⮕ Promise\u003cReturnType\u003ctypeof doSomething\u003e\u003e\n\ntimeout(\n  new AbortablePromise(resolve =\u003e {\n    // Resolve the promise value\n  }),\n  100\n);\n```\n","project_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fsmikhalevski%2Fparallel-universe","html_url":"https://awesome.ecosyste.ms/projects/github.com%2Fsmikhalevski%2Fparallel-universe","lists_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fsmikhalevski%2Fparallel-universe/lists"}