{"id":15651712,"url":"https://github.com/garronej/run_exclusive","last_synced_at":"2025-03-27T07:31:35.101Z","repository":{"id":40295311,"uuid":"99932858","full_name":"garronej/run_exclusive","owner":"garronej","description":"⚡🔒 Wait queue for function execution 🔒 ⚡","archived":false,"fork":false,"pushed_at":"2024-05-03T10:26:31.000Z","size":572,"stargazers_count":32,"open_issues_count":4,"forks_count":1,"subscribers_count":2,"default_branch":"main","last_synced_at":"2025-03-17T22:44:40.636Z","etag":null,"topics":["async-await","callback","deno","lock","mutex","promise","queued-calls","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/garronej.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}},"created_at":"2017-08-10T14:36:40.000Z","updated_at":"2024-05-17T08:55:31.000Z","dependencies_parsed_at":"2024-05-03T11:49:46.509Z","dependency_job_id":"c90f9d37-e3ba-483a-bfbd-7aedb8f2ec8e","html_url":"https://github.com/garronej/run_exclusive","commit_stats":{"total_commits":156,"total_committers":5,"mean_commits":31.2,"dds":0.3141025641025641,"last_synced_commit":"eba816285665c5fa17f7bb0fd9eaebbeaeecfbff"},"previous_names":[],"tags_count":25,"template":false,"template_full_name":null,"repository_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/garronej%2Frun_exclusive","tags_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/garronej%2Frun_exclusive/tags","releases_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/garronej%2Frun_exclusive/releases","manifests_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/garronej%2Frun_exclusive/manifests","owner_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners/garronej","download_url":"https://codeload.github.com/garronej/run_exclusive/tar.gz/refs/heads/main","host":{"name":"GitHub","url":"https://github.com","kind":"github","repositories_count":245802692,"owners_count":20674727,"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-await","callback","deno","lock","mutex","promise","queued-calls","typescript"],"created_at":"2024-10-03T12:39:51.277Z","updated_at":"2025-03-27T07:31:30.092Z","avatar_url":"https://github.com/garronej.png","language":"TypeScript","funding_links":[],"categories":["TypeScript"],"sub_categories":[],"readme":"\u003cp align=\"center\"\u003e\n    \u003cimg src=\"https://user-images.githubusercontent.com/6702424/74085997-1d3c1400-4a7f-11ea-9abf-81a4352f827f.png\"\u003e  \n\u003c/p\u003e\n\u003cp align=\"center\"\u003e\n    ⚡🔒 \u003ci\u003e Generate functions that do not allow parallel executions\u003c/i\u003e 🔒 ⚡\n    \u003cbr\u003e\n    \u003cbr\u003e\n    \u003ca href=\"https://github.com/garronej/run-exclusive/actions\"\u003e\n      \u003cimg src=\"https://github.com/garronej/run_exclusive/workflows/ci/badge.svg?branch=develop\"\u003e\n    \u003c/a\u003e\n    \u003ca href=\"https://bundlephobia.com/package/run-exclusive\"\u003e\n      \u003cimg src=\"https://img.shields.io/bundlephobia/minzip/run-exclusive\"\u003e\n    \u003c/a\u003e\n    \u003ca href=\"https://www.npmjs.com/package/run-exclusive\"\u003e\n      \u003cimg src=\"https://img.shields.io/npm/dw/run-exclusive\"\u003e\n    \u003c/a\u003e\n    \u003ca href=\"https://www.npmjs.com/package/run-exclusive\"\u003e\n      \u003cimg src=\"https://img.shields.io/npm/v/run-exclusive?logo=npm\"\u003e\n    \u003c/a\u003e\n    \u003ca href=\"https://deno.land/x/run_exclusive\"\u003e\n        \u003cimg src=\"https://img.shields.io/endpoint?url=https%3A%2F%2Fdeno-visualizer.danopia.net%2Fshields%2Flatest-version%2Fx%2Frun_exclusive%2Fmod.ts\"\u003e\n    \u003c/a\u003e  \n    \u003ca href=\"https://github.com/garronej/run-exclusive/blob/main/LICENSE\"\u003e\n      \u003cimg src=\"https://img.shields.io/npm/l/run-exclusive\"\u003e\n    \u003c/a\u003e\n\u003c/p\u003e  \n\n---\n\nLet you create functions that enforce no more than one execution happens at the same time.  \nIf the function is called again while there is already an execution ongoing the new call will be queued and executed once all the queued calls have completed.\n\nThis is a higher-level approach to the problem addressed by [`DirtyHairy/async-mutex`](https://www.npmjs.com/package/async-mutex).    \n\n\u003cb\u003eSuitable for any JS runtime env (deno, node, old browser, react-native ...)\u003c/b\u003e\n- ✅ It is both a [Deno](https://deno.land/x/run_exclusive) and an [NPM](https://www.npmjs.com/run-exclusive) module. \n- ✅ Lightweight, no dependency.\n- ✅ No polyfills needed, the NPM module is transpiled down to ES3   \n\n# Examples\n\n## Basic example\n\n```typescript\nimport * as runExclusive from \"run-exclusive\";\n\nconst f= runExclusive.build(async ()=\u003e {\n\n    await new Promise(resolve=\u003e setTimeout(resolve, 1000));\n\n    console.log(\"Hello world\");\n\n});\n\nf();\nf();\n```\nResult:  \n```bash\n# One second..\nHello World\n# One second\nHello World\n```\n\n## Group of mutually run exclusive functions\n\n```typescript\nimport * as runExclusive from \"run-exclusive\";\n\nconst groupRef= runExclusive.createGroupRef();\n\nconst f1= runExclusive.build(groupRef, async ()=\u003e {\n\n    await new Promise(resolve=\u003e setTimeout(resolve, 1000));\n\n    console.log(\"Hello world 1\");\n\n});\n\nconst f2= runExclusive.build(groupRef, async ()=\u003e {\n\n    await new Promise(resolve=\u003e setTimeout(resolve, 1000));\n\n    console.log(\"Hello world 2\");\n\n});\n\nf1();\nf2();\n```\nResult:  \n```bash\n# One second..\nHello World 1\n# One second\nHello World 2\n```\n\n# Install / Import\n\n## Deno\n\n```typescript\nimport * as runExclusive from \"https://deno.land/x/run_exclusive/mod.ts\";\n```\n\n## Other javascript runtime environnement: \n\n```bash\n$ npm install --save run-exclusive\n```\n```typescript\nimport * as runExclusive from \"run-exclusive\";\n```\n\n# Try it now\n\nThanks to Stackblitz you can try this lib within your browser like if you where in VSCode. \n\n![Screenshot 2020-02-14 at 12 48 04](https://user-images.githubusercontent.com/6702424/74528376-70531280-4f28-11ea-9545-46d258b74454.png)\n\n[__Run the example__](https://stackblitz.com/edit/run-exclusive-hello-world?embed=1\u0026file=index.ts)\n\n# Table of content\n\n- [Examples](#examples)\n  - [Basic example](#basic-example)\n  - [Group of mutually run exclusive functions](#group-of-mutually-run-exclusive-functions)\n- [Install / Import](#install--import)\n  - [Deno](#deno)\n  - [Other javascript runtime environnement:](#other-javascript-runtime-environnement)\n  - [Import from HTML, with CDN](#import-from-html-with-cdn)\n- [Try it now](#try-it-now)\n- [Table of content](#table-of-content)\n- [Documentation](#documentation)\n  - [``build()``](#build)\n  - [``createGroupRef()``](#creategroupref)\n  - [``buildMethod()``](#buildmethod)\n  - [``buildCb()`` and ``buildMethodCb()``](#buildcb-and-buildmethodcb)\n  - [Queued calls](#queued-calls)\n    - [``getQueuedCallCount()``](#getqueuedcallcount)\n    - [``cancelAllQueuedCalls()``](#cancelallqueuedcalls)\n    - [``isRunning()``](#isrunning)\n    - [``getPrComplete()``](#getprcomplete)\n\n# Documentation\n\n## ``build()``\n\nLet us compare regular functions with `run-exclusive` functions.\n\n````typescript\nlet alphabet= \"\";\n\n//This function wait a random time then append a letter to alphabet.\nasync function spell(letter: string): Promise\u003cstring\u003e{\n\n    await new Promise(\n        resolve=\u003e setTimeout(\n            resolve, \n            Math.random()*100\n        )\n    );\n\n    alphabet+=letter;\n\n    return alphabet;\n\n}\n\nspell(\"a\");\nspell(\"b\");\nspell(\"c\").then( message =\u003e console.log(message)); \n//We cant predict what will be printed to the console,\n//it can be \"c\", \"ca\", \"ac\", \"cb\", \"bc\", \"cab\", \"cba\", \"bac\", \"bca\", \"acb\" or \"abc\"\n\n````\nNow the same example using ``run-exclusive``:  \n\n````typescript\nimport * as runExclusive from \"run-exclusive\";\n\nlet alphabet= \"\";\n\nconst spell= runExclusive.build(\n    async (letter: string): Promise\u003cstring\u003e =\u003e {\n\n        await new Promise(\n            resolve=\u003esetTimeout(\n                resolve, \n                Math.random()*100\n            )\n        ); \n\n        alphabet+=letter;\n\n        return alphabet;\n\n    }\n);\n\nspell(\"a\");\nspell(\"b\");\nspell(\"c\").then( message =\u003e console.log(message)); // Always prints \"abc\"\n\n````\n\nThe types definition of the function passed as argument are conserved.\n![Screenshot 2020-02-08 at 15 42 09](https://user-images.githubusercontent.com/6702424/74087111-9a6c8680-4a89-11ea-99f5-d5db809835f2.png)\n\n## ``createGroupRef()``\n\nTo share a unique lock among a group of functions.\n\n````typescript\nimport * as runExclusive from \"run-exclusive\";\n\nlet alphabet= \"\";\n\nconst groupSpelling= runExclusive.createGroupRef();\n\nconst spellUpperCase= runExclusive.build(groupSpelling\n    async (letter: string) =\u003e {\n\n        await new Promise\u003cvoid\u003e(resolve=\u003e setTimeout(resolve, Math.random()*100));\n\n        alphabet+=letter.toUpperCase();\n\n    }\n);\n\nconst spellLowerCase= runExclusive.build(groupSpelling\n    async (letter: string) =\u003e {\n\n        await new Promise\u003cvoid\u003e(resolve=\u003e setTimeout(resolve, Math.random()*100));\n\n        alphabet+=letter.toLowerCase();\n\n    }\n);\n\nspell(\"a\");\nspellUpperCase(\"b\");\nspell(\"c\").then(()=\u003e console.log(alphabet)); //prints \"aBc\".\n````\n\n## ``buildMethod()``\n\nIf you define run exclusive class methods chances are you want the lock to be restricted\nto the class's object instance.  \nThis is what ``buildMethod()``is for.\n\n````typescript\n\nclass Student {\n\n    public alphabet= \"\";\n\n    public spell= runExclusive.buildMethod(\n       async (letter: string) =\u003e {\n\n            await new Promise\u003cvoid\u003e(resolve=\u003e setTimeout(resolve, 1000));\n\n            this.alphabet+=letter.toLowerCase();\n\n        }\n    );\n\n}\n\nconst alice= new Student();\nconst bob= new Student();\n\nalice.spell(\"A\");\nbob.spell(\"a\");\nalice.spell(\"B\");\nbob.spell(\"b\");\nalice.spell(\"C\").then( ()=\u003e console.log(alice.alphabet)); //prints after 3s: \"ABC\"\nbob.spell(\"c\").then( ()=\u003e console.log(bob.alphabet)); //prints after 3s: \"abc\"\n\n````\n\n## ``buildCb()`` and ``buildMethodCb()``\n\n`buildCb()` is the pending of `build()` for creating run exclusive functions that complete by invoking a callback. (Instead of resolving a promise).\n\nThe only valid reason to use this instead of `build()` is to be able to retrieve the result synchronously. \n\n\u003cb\u003e\u003cspan style=\"color:red\"\u003e WARNING:\u003c/span\u003e\u003c/b\u003e If you make the callback optional the argument before it cannot be a function.  \nBe aware that the compiler won't warn you against it.  \nExample: ``(getLetter: ()=\u003e string, callback?: (message: string)=\u003e voidA) =\u003e {..}``  \nis NOT a valid function to pass to ``buildCb()`` or ``buildMethodCb()``.\n*Thanks @AnyhowStep*   \n\n\u003cb\u003eWARNING:\u003c/b\u003e The function should never throw as the exception wont be catchable.\n\n````typescript\n\nlet alphabet= \"\";\n\nconst spell= runExclusive.buildCb(\n    (letter: string, callback?: (message: string)=\u003e void) =\u003e {\n\n        setTimeout(()=\u003e{\n\n            alphabet+= letter;\n\n            /*\n            Callback must always be called, event if the user \n            does not provide one, it is the only way for the module\n            to know that the function has completed it's execution.\n            You can assume that the callback function is not undefined.\n            To tell if the user has provided à callback you can access (callback as any).hasCallback;\n            */\n            callback!(alphabet);\n\n        }, Math.rand()*100);\n\n    }\n};\n\nspell(\"a\");\nspell(\"b\");\nspell(\"c\", message =\u003e console.log(message)); // prints \"abc\"\n\n````\n\nNOTE: ``runExclusive.buildMethodCb()`` also available.\n\n## Queued calls \n\nIt is possible to check, for a given run exclusive function, if there is currently\nan ongoing execution and how many calls are queued.\nIt is also possible to cancel the queued calls.\n\n### ``getQueuedCallCount()``\n\n Get the number of queued call of a run-exclusive function. \n Note that if you call a runExclusive function and call this \n directly after it will return 0 as there is one function call\n execution ongoing but 0 queued.  \n \n The classInstanceObject parameter is to provide only for the run-exclusive\n function created with 'buildMethod[Cb].\n\n```typescript\nexport declare function getQueuedCallCount(\n    runExclusiveFunction: Function, \n    classInstanceObject?: Object\n): number;\n```\n\n### ``cancelAllQueuedCalls()``\nCancel all queued calls of a run-exclusive function.\nNote that the current running call will not be cancelled.  \n\nThe classInstanceObject parameter is to provide only for the run-exclusive\nfunction created with 'buildMethod[Cb].\n```typescript\nexport declare function cancelAllQueuedCalls(\n    runExclusiveFunction: Function, \n    classInstanceObject?: Object\n): number;\n```\n### ``isRunning()``\n\nTell if a run-exclusive function has an instance of it's call currently being\nperformed.\n\nThe classInstanceObject parameter is to provide only for the run-exclusive\nfunction created with 'buildMethod[Cb].\n```typescript\nexport declare function isRunning(\n    runExclusiveFunction: Function, \n    classInstanceObject?: Object\n): boolean;\n```\n\n### ``getPrComplete()``\n\nReturn a promise that resolve when all the current queued call of a runExclusive functions have completed.  \nThe classInstanceObject parameter is to provide only for the run-exclusive\nfunction created with 'buildMethod[Cb].\n\n````typescript\nexport declare function getPrComplete(\n    runExclusiveFunction: Function, \n    classInstanceObject?: Object\n): Promise\u003cvoid\u003e;\n````\n","project_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fgarronej%2Frun_exclusive","html_url":"https://awesome.ecosyste.ms/projects/github.com%2Fgarronej%2Frun_exclusive","lists_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fgarronej%2Frun_exclusive/lists"}