{"id":44665496,"url":"https://github.com/heap-code/concurrency-synchronization","last_synced_at":"2026-02-15T00:21:26.818Z","repository":{"id":176267548,"uuid":"655236613","full_name":"heap-code/concurrency-synchronization","owner":"heap-code","description":"Manage concurrency in Javascript \"threads\" with promises","archived":false,"fork":false,"pushed_at":"2026-02-06T02:29:53.000Z","size":5466,"stargazers_count":1,"open_issues_count":3,"forks_count":0,"subscribers_count":0,"default_branch":"master","last_synced_at":"2026-02-06T11:55:01.070Z","etag":null,"topics":["concurrency","mutex","producer-consumer","promise","semaphore","synchronization","typescript"],"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/heap-code.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":"support/sleep.ts","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":"2023-06-18T09:58:17.000Z","updated_at":"2026-02-06T02:28:51.000Z","dependencies_parsed_at":"2024-04-15T22:50:18.319Z","dependency_job_id":"6d5fc618-cf3b-47aa-a04e-246be2d80bf3","html_url":"https://github.com/heap-code/concurrency-synchronization","commit_stats":null,"previous_names":["heap-code/concurrency-synchronization"],"tags_count":11,"template":false,"template_full_name":null,"purl":"pkg:github/heap-code/concurrency-synchronization","repository_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/heap-code%2Fconcurrency-synchronization","tags_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/heap-code%2Fconcurrency-synchronization/tags","releases_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/heap-code%2Fconcurrency-synchronization/releases","manifests_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/heap-code%2Fconcurrency-synchronization/manifests","owner_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners/heap-code","download_url":"https://codeload.github.com/heap-code/concurrency-synchronization/tar.gz/refs/heads/master","sbom_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/heap-code%2Fconcurrency-synchronization/sbom","scorecard":null,"host":{"name":"GitHub","url":"https://github.com","kind":"github","repositories_count":286080680,"owners_count":29461775,"icon_url":"https://github.com/github.png","version":null,"created_at":"2022-05-30T11:31:42.601Z","updated_at":"2026-02-14T22:42:09.113Z","status":"ssl_error","status_checked_at":"2026-02-14T22:42:05.053Z","response_time":53,"last_error":"SSL_read: 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":["concurrency","mutex","producer-consumer","promise","semaphore","synchronization","typescript"],"created_at":"2026-02-15T00:21:22.376Z","updated_at":"2026-02-15T00:21:26.810Z","avatar_url":"https://github.com/heap-code.png","language":"TypeScript","funding_links":[],"categories":[],"sub_categories":[],"readme":"# Concurrency synchronization\n\n[![CI](https://github.com/heap-code/concurrency-synchronization/actions/workflows/ci.yml/badge.svg?branch=master)](https://github.com/heap-code/concurrency-synchronization/actions/workflows/ci.yml)\n[![npm version](https://img.shields.io/npm/v/@heap-code/concurrency-synchronization)](https://www.npmjs.com/package/@heap-code/concurrency-synchronization)\n![Code coverage](.badges/code/coverage.svg)\n![Comment coverage](.badges/comment/coverage.svg)\n\nManage concurrency in Javascript _\"threads\"_ with promises.\n\n## Preface\n\nThe aim of this package is to **mimic** the various tools used for concurrency synchronization,\nas multithreading does not exist in Javascript in the same way as in _C_, _Java_ or other languages.\n\n\u003e From the [mozilla documentation](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Promise#promise_concurrency\u003e):\n\u003e\n\u003e Note that JavaScript is **single-threaded by nature**,\n\u003e so at a given instant, only one task will be executing,\n\u003e although control can shift between different promises,\n\u003e making execution of the promises **appear concurrent**.\n\u003e Parallel execution in JavaScript can only be achieved through worker threads.\n\nA _\"threads\"_ is, in this context, an asynchronous task.\n\n## Installation\n\nSimply run:\n\n```bash\nnpm i @heap-code/concurrency-synchronization\n```\n\n### CDN\n\nThanks to [_jsdelivr_](https://www.jsdelivr.com/),\nthis package can easily be used in browsers like this:\n\n```html\n\u003cscript\n src=\"https://cdn.jsdelivr.net/npm/@heap-code/concurrency-synchronization/dist/bundles/concurrency-synchronization.umd.js\"\n type=\"application/javascript\"\n\u003e\u003c/script\u003e\n```\n\n\u003e **Note:**  \n\u003e It is recommended to use a minified and versioned bundle.\n\u003e\n\u003e For example:\n\u003e\n\u003e ```html\n\u003e \u003cscript\n\u003e  src=\"https://cdn.jsdelivr.net/npm/@heap-code/concurrency-synchronization@1.0.1/dist/bundles/concurrency-synchronization.umd.min.js\"\n\u003e  type=\"application/javascript\"\n\u003e \u003e\u003c/script\u003e\n\u003e ```\n\nMore at this [_jsdelivr_ package page](https://www.jsdelivr.com/package/npm/@heap-code/concurrency-synchronization).\n\n## Usages\n\nIn the code examples, the `sleep` function is the following:\n\n```typescript\nfunction sleep(time: number) {\n  return new Promise(resolve =\u003e setTimeout(resolve, time));\n}\n```\n\n**This is a placeholder for any asynchronous task.**\n\n\u003e **Note:**  \n\u003e _Avoid_ using this package on _\"production\"_ code.  \n\u003e Go [here](#when-to-use) to understand why.\n\n### Mutex\n\nA mutex is a mechanism that enforces limits on access to a resource.\nIt generally protects the access to shared variables.\n\n\u003e Use [semaphores](#semaphore) for synchronization rather than a mutex.\n\n---\n\nWith the given example:\n\n```typescript\nconst myVar = { i: 0 };\n\nasync function do1() {\n  await sleep(200);\n  myVar.i += 1;\n}\n\nasync function do2() {\n  await sleep(50);\n  myVar.i *= 3;\n}\n\nasync function bootstrap() {\n  await Promise.all([do1(), sleep(10).then(() =\u003e do2())]);\n  console.log(myVar.i); // =\u003e 1\n}\nbootstrap();\n```\n\nEven with the `sleep`, we could expect that `myVar.i += 1` is done\nbefore `myVar.i *= 3` as it is called before.  \nHowever `myVar.i` is not protected, then the final value is `1`.\n\n---\n\nIf a mutex locks the task, then the variable is protected:\n\n```typescript\nimport { Mutex } from \"@heap-code/concurrency-synchronization\";\n\nconst mutex = new Mutex();\nconst myVar = { i: 0 };\n\nasync function do1() {\n  await mutex.lock();\n\n  await sleep(200);\n  myVar.i += 1;\n\n  await mutex.unlock();\n}\n\nasync function do2() {\n  await mutex.lock();\n\n  await sleep(50);\n  myVar.i *= 3;\n\n  await mutex.unlock();\n}\n\nasync function bootstrap() {\n  await Promise.all([do1(), sleep(10).then(() =\u003e do2())]);\n  console.log(myVar.i); // =\u003e 3\n}\nbootstrap();\n```\n\n\u003e From this [wikipedia section](https://en.wikipedia.org/wiki/Lock_(computer_science)#Mutexes_vs._semaphores):\n\u003e\n\u003e The task that locked the mutex is supposed to unlock it.\n\n#### Mutex tryLock\n\nIt is possible to try to lock a mutex in a given time limit.  \nThe function will then throw an exception if the mutex could not lock in time:\n\n```typescript\nimport { ConcurrencyExceedTimeoutException } from \"@heap-code/concurrency-synchronization\";\n\nmutex.tryLock(250).catch((error: unknown) =\u003e {\n  if (error instanceof ConcurrencyExceedTimeoutException) {\n    console.log(\"Could not lock in the given time.\");\n  }\n\n  throw error;\n});\n```\n\n#### Mutex interrupt\n\nA mutex can be interrupted at any time.  \nAll awaiting _\"threads\"_ will then receive an exception:\n\n```typescript\nimport { ConcurrencyInterruptedException, Mutex } from \"@heap-code/concurrency-synchronization\";\n\nconst mutex = new Mutex();\nconst myVar = { i: 0 };\n\nasync function do1() {\n  await mutex.lock().catch((error: unknown) =\u003e {\n    if (error instanceof ConcurrencyInterruptedException) {\n      console.log(\"The mutex has been interrupted\", error.getReason());\n    }\n\n    throw error;\n  });\n\n  await sleep(200);\n  myVar.i += 1;\n\n  await mutex.unlock();\n}\n\nasync function bootstrap() {\n  await Promise.all([\n    do1(),\n    do1(),\n    sleep(20).then(() =\u003e mutex.interrupt({ message: \"Take too much time\" }))\n  ]);\n}\nbootstrap();\n```\n\n### Semaphore\n\n\u003e From [wikipedia](https://en.wikipedia.org/wiki/Semaphore_(programming)):\n\u003e\n\u003e Semaphores are a type of synchronization primitive.\n\nThey can be used to protect certain resources (like mutexes),\nbut are generally used for synchronization:\n\n```typescript\nimport { Semaphore } from \"@heap-code/concurrency-synchronization\";\n\nasync function bootstrap() {\n  const semaphore = new Semaphore(0);\n\n  const time1 = 100;\n  const time2 = 150;\n\n  sleep(time1).then(() =\u003e semaphore.release());\n  sleep(time2).then(() =\u003e semaphore.release());\n\n  const maxTime = Math.max(time1, time2);\n\n  const before = performance.now();\n  await semaphore.acquire(2); // waiting until releases\n  const after = performance.now();\n\n  const elapsed = after - before; // ~150\n  console.log(\"Done. took %dms with expected %dms\", elapsed, maxTime);\n}\nbootstrap();\n```\n\n#### Semaphore tryAcquire\n\nIt is possible to try to acquire a semaphore in a given time limit.  \nThe function will then throw an exception if the semaphore could not acquire in time:\n\n ```typescript\nimport {\n  ConcurrencyExceedTimeoutException,\n  Semaphore\n} from \"@heap-code/concurrency-synchronization\";\n\nasync function bootstrap() {\n  const semaphore = new Semaphore(2);\n\n  const acquired1 = await semaphore.tryAcquire(100).then(() =\u003e true);\n  const acquired2 = await semaphore.tryAcquire(100, 2).catch((error: unknown) =\u003e {\n    if (error instanceof ConcurrencyExceedTimeoutException) {\n      return false;\n    }\n  \n    throw error;\n  });\n\n  console.log(acquired1); // true\n  console.log(acquired2); // false\n}\nbootstrap();\n```\n\n#### Semaphore interrupt\n\nA semaphore can be interrupted at any time.  \nAll awaiting _\"threads\"_ will then receive an exception:\n\n ```typescript\nimport { ConcurrencyInterruptedException, Semaphore } from \"@heap-code/concurrency-synchronization\";\n\nasync function bootstrap() {\n  const semaphore = new Semaphore(1);\n\n  void sleep(100).then(() =\u003e semaphore.interrupt({ code: 502 }, 2));\n\n  const succeed = await Promise.all([\n    semaphore.acquire(),\n    semaphore.acquire(2),\n    semaphore.tryAcquire(200),\n    semaphore.tryAcquire(200, 1)\n  ]).catch((error: unknown) =\u003e {\n    if (error instanceof ConcurrencyInterruptedException) {\n      return false;\n    }\n    throw error;\n  });\n\n  console.log(succeed); // false\n  console.log(semaphore.permitsAvailable); // 2\n}\nbootstrap();\n```\n\n#### Semaphore releaseAll\n\nVery similar to [interrupt](#semaphore-interrupt),\nbut it does not throw an exception.\n\n ```typescript\nimport { Semaphore } from \"@heap-code/concurrency-synchronization\";\n\nasync function bootstrap() {\n  const semaphore = new Semaphore(1);\n\n  void sleep(100).then(() =\u003e semaphore.releaseAll(3));\n\n  await Promise.all([\n    semaphore.acquire(),\n    semaphore.acquire(2),\n    semaphore.tryAcquire(200),\n    semaphore.tryAcquire(200, 1)\n  ]);\n \n  console.log(\"ok\");\n  console.log(semaphore.permitsAvailable); // 3\n}\nbootstrap()\n```\n\n\u003e **Note:**\n\u003e Unless it is really desired, prefer [interrupt](#semaphore-interrupt) over `releaseAll`.\n\n### Producer-Consumer\n\nThe `ProducerConsumer` looks a lot like a [Semaphore](#semaphore),\nbut it returns values on _acquire_.\n\nBy default, all readings use an array:\n\n```typescript\nimport { ProducerConsumer } from \"@heap-code/concurrency-synchronization\";\n\nasync function bootstrap() {\n  const producerConsumer = new ProducerConsumer([1]);\n\n  const time1 = 100;\n  const time2 = 150;\n\n  sleep(time1).then(() =\u003e producerConsumer.write(3, 4));\n  sleep(time2).then(() =\u003e producerConsumer.write(2));\n\n  const maxTime = Math.max(time1, time2);\n\n  const before = performance.now();\n  const valuesRead = await producerConsumer.read(4); // waiting until all is read\n  const after = performance.now();\n\n  const elapsed = after - before; // ~150\n  console.log(\"Done. took %dms with expected %dms\", elapsed, maxTime);\n  console.log(valuesRead) // [1, 3, 4, 2]\n}\nbootstrap();\n```\n\n#### Producer-Consumer tryRead\n\nIt is possible to try to read some values in a given time limit.  \nThe function will then throw an exception if it could not read in time:\n\n```typescript\nimport {\n  ConcurrencyExceedTimeoutException,\n  ProducerConsumer\n} from \"@heap-code/concurrency-synchronization\";\n\nasync function bootstrap() {\n  const producerConsumer = new ProducerConsumer([1, 2, 3]);\n\n  const success1 = await producerConsumer.tryRead(100, 2).then(() =\u003e true);\n  const success2 = await producerConsumer.tryRead(100, 2).catch((error: unknown) =\u003e {\n    if (error instanceof ConcurrencyExceedTimeoutException) {\n      return false;\n    }\n  \n    throw error;\n  });\n\n  console.log(success1); // true\n  console.log(success2); // false\n}\nbootstrap();\n```\n\n#### Producer-Consumer readOne\n\nThe `read` and `tryRead` have their \"one\"-method\nthat do the same thing but return only one value instead of an array:\n\n```typescript\nimport { ProducerConsumer } from \"@heap-code/concurrency-synchronization\";\n\nasync function bootstrap() {\n  const producerConsumer = new ProducerConsumer([1, 2]);\n\n  // const [value1] = producerConsumer.read(1);\n  // can be written:\n  const value1 = await producerConsumer.readOne();\n\n  // const [value2] = producerConsumer.tryRead(100, 1);\n // can be written:\n  const value2 = await producerConsumer.tryReadOne(100);\n  \n  console.log(value1, value2); // 1 2\n}\nbootstrap();\n```\n\n#### Producer-Consumer interrupt\n\nA `ProducerConsumer` can be interrupted at any time.  \nAll awaiting _\"threads\"_ will then receive an exception:\n\n```typescript\nimport {\n  ConcurrencyInterruptedException,\n  ProducerConsumer\n} from \"@heap-code/concurrency-synchronization\";\n\nasync function bootstrap() {\n  const producerConsumer = new ProducerConsumer([1]);\n\n  void sleep(100).then(() =\u003e producerConsumer.interrupt({ code: 502 }, [1, 2, 3]));\n\n  const succeed = await Promise.all([\n    producerConsumer.read(3),\n    producerConsumer.readOne(),\n    producerConsumer.tryRead(200, 3),\n    producerConsumer.tryReadOne(200)\n  ]).catch((error: unknown) =\u003e {\n    if (error instanceof ConcurrencyInterruptedException) {\n      return false;\n    }\n    throw error;\n  });\n\n  console.log(succeed); // false\n  console.log(producerConsumer.permitsAvailable); // 3\n}\nbootstrap();\n```\n\n## When to use\n\nThis package can be useful when writing test and wanting to synchronize events.\n\nFor example, the [RxJs observable](https://rxjs.dev/guide/observable) behavior slightly differs\nif it comes from _regular_ observable or [subjects](https://rxjs.dev/guide/subject).\n\n\u003e [firstValueFrom](https://rxjs.dev/api/index/function/firstValueFrom) returns the value immediately.\n\nSo this difference can be omitted with the following:\n\n```typescript\nimport { ProducerConsumer } from \"@heap-code/concurrency-synchronization\";\n\ndescribe(\"My test\", () =\u003e {\n  it(\"should work\", async () =\u003e {\n    const producerConsumer = new ProducerConsumer();\n    const subscription = myObservable.subscribe(value =\u003e producerConsumer.write(value));\n    // something that updates the observable\n \n    // Need to pass 2 times in the event\n    const [r1, r2] = await producerConsumer.tryRead(500, 2);\n \n    expect(r1).toBe(1);\n    expect(r2).toBe(2);\n    subscription.unsubscribe()\n  });\n});\n```\n\n---\n\n**However**, these synchronizations are **generally** not wanted in production code as it is wanted to\nkeep the javascript code as _\"parallelized\"_ as possible, to not block code branches.  \nMoreover, better solutions might exist for these problems,\nsuch as `firstValueFrom` and `lastValueFrom` for RxJs.\n\n## Releases\n\nSee information about breaking changes and release notes [here](https://github.com/heap-code/concurrency-synchronization/blob/HEAD/CHANGELOG.md).\n","project_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fheap-code%2Fconcurrency-synchronization","html_url":"https://awesome.ecosyste.ms/projects/github.com%2Fheap-code%2Fconcurrency-synchronization","lists_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fheap-code%2Fconcurrency-synchronization/lists"}