@chriscdn/promise-semaphore 3.0.1 → 3.1.2
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +39 -53
- package/lib/promise-semaphore.cjs +1 -1
- package/lib/promise-semaphore.cjs.map +1 -1
- package/lib/promise-semaphore.modern.js +1 -1
- package/lib/promise-semaphore.modern.js.map +1 -1
- package/lib/promise-semaphore.module.js +1 -1
- package/lib/promise-semaphore.module.js.map +1 -1
- package/lib/semaphore.d.ts +14 -7
- package/package.json +6 -4
- package/__tests__/semaphore.test.ts +0 -147
- package/src/group-semaphore.ts +0 -46
- package/src/index.ts +0 -2
- package/src/semaphore.ts +0 -173
- package/tsconfig.json +0 -5
package/README.md
CHANGED
|
@@ -21,9 +21,7 @@ yarn add @chriscdn/promise-semaphore
|
|
|
21
21
|
|
|
22
22
|
Version 3 introduces two main changes:
|
|
23
23
|
|
|
24
|
-
- A new `GroupSemaphore` class has been added. It allows multiple tasks within
|
|
25
|
-
the same group (identified by a key) to run concurrently while ensuring that
|
|
26
|
-
only one group's tasks are active at a time. See below for documentation.
|
|
24
|
+
- A new `GroupSemaphore` class has been added. It allows multiple tasks within the same group (identified by a key) to run concurrently while ensuring that only one group's tasks are active at a time. See below for documentation.
|
|
27
25
|
- The default export has been replaced with a named export.
|
|
28
26
|
|
|
29
27
|
Change:
|
|
@@ -42,47 +40,47 @@ import { Semaphore } from "@chriscdn/promise-semaphore";
|
|
|
42
40
|
|
|
43
41
|
### Create an instance
|
|
44
42
|
|
|
45
|
-
```
|
|
43
|
+
```ts
|
|
46
44
|
import { Semaphore } from "@chriscdn/promise-semaphore";
|
|
47
45
|
const semaphore = new Semaphore([maxConcurrent]);
|
|
48
46
|
```
|
|
49
47
|
|
|
50
|
-
The `maxConcurrent` parameter is optional and defaults to `1` (making it an
|
|
51
|
-
exclusive lock or _binary semaphore_). An integer greater than `1` can be used
|
|
52
|
-
to allow multiple concurrent executions from separate iterations of the event
|
|
53
|
-
loop.
|
|
48
|
+
The `maxConcurrent` parameter is optional and defaults to `1` (making it an exclusive lock or _binary semaphore_). An integer greater than `1` can be used to allow multiple concurrent executions from separate iterations of the event loop.
|
|
54
49
|
|
|
55
50
|
### Acquire a lock
|
|
56
51
|
|
|
57
|
-
```
|
|
58
|
-
semaphore.acquire([
|
|
52
|
+
```ts
|
|
53
|
+
semaphore.acquire([options]);
|
|
59
54
|
```
|
|
60
55
|
|
|
61
|
-
This returns a `Promise` that resolves
|
|
62
|
-
|
|
63
|
-
|
|
56
|
+
This method returns a `Promise` that resolves when the lock is acquired.
|
|
57
|
+
|
|
58
|
+
The `options` parameter is optional and can be:
|
|
59
|
+
|
|
60
|
+
- A **key** (`string` or `number`): This lets a single `Semaphore` instance manage locks in different contexts (see the second example for `key` usage).
|
|
61
|
+
- An **object** with the following properties (all optional):
|
|
62
|
+
- `key` (`string` or `number`): Functions the same as above.
|
|
63
|
+
- `priority` (`number`): Determines the order in which queued requests are processed. Higher values are processed first.
|
|
64
64
|
|
|
65
65
|
### Release a lock
|
|
66
66
|
|
|
67
|
-
```
|
|
67
|
+
```ts
|
|
68
68
|
semaphore.release([key]);
|
|
69
69
|
```
|
|
70
70
|
|
|
71
|
-
The `release` method should be called within a `finally` block (whether using
|
|
72
|
-
promises or a `try/catch` block) to ensure the lock is released.
|
|
71
|
+
The `release` method should be called within a `finally` block (whether using promises or a `try/catch` block) to ensure the lock is released. It's crucial to call `release` with the same `key` the lock was acquired with.
|
|
73
72
|
|
|
74
73
|
### Check if a lock can be acquired
|
|
75
74
|
|
|
76
|
-
```
|
|
75
|
+
```ts
|
|
77
76
|
semaphore.canAcquire([key]);
|
|
78
77
|
```
|
|
79
78
|
|
|
80
|
-
This synchronous method returns `true` if a lock can be immediately acquired,
|
|
81
|
-
and `false` otherwise.
|
|
79
|
+
This synchronous method returns `true` if a lock can be immediately acquired, and `false` otherwise.
|
|
82
80
|
|
|
83
81
|
### `count`
|
|
84
82
|
|
|
85
|
-
```
|
|
83
|
+
```ts
|
|
86
84
|
semaphore.count([key]);
|
|
87
85
|
```
|
|
88
86
|
|
|
@@ -90,16 +88,15 @@ This function is synchronous, and returns the current number of locks.
|
|
|
90
88
|
|
|
91
89
|
### `request` method
|
|
92
90
|
|
|
93
|
-
```
|
|
94
|
-
const results = await semaphore.request(fn [,
|
|
91
|
+
```ts
|
|
92
|
+
const results = await semaphore.request(fn [, options]);
|
|
95
93
|
```
|
|
96
94
|
|
|
97
|
-
This function reduces boilerplate when using `acquire` and `release`. It returns
|
|
98
|
-
a promise that resolves when `fn` completes. It is functionally equivalent to:
|
|
95
|
+
This function reduces boilerplate when using `acquire` and `release`. It returns a promise that resolves when `fn` completes. It is functionally equivalent to:
|
|
99
96
|
|
|
100
|
-
```
|
|
97
|
+
```ts
|
|
101
98
|
try {
|
|
102
|
-
await semaphore.acquire([
|
|
99
|
+
await semaphore.acquire([options]);
|
|
103
100
|
return await fn();
|
|
104
101
|
} finally {
|
|
105
102
|
semaphore.release([key]);
|
|
@@ -108,23 +105,23 @@ try {
|
|
|
108
105
|
|
|
109
106
|
### `requestIfAvailable` method
|
|
110
107
|
|
|
111
|
-
```
|
|
112
|
-
const results = await semaphore.requestIfAvailable(fn [,
|
|
108
|
+
```ts
|
|
109
|
+
const results = await semaphore.requestIfAvailable(fn [, options]);
|
|
113
110
|
```
|
|
114
111
|
|
|
115
112
|
This is functionally equivalent to:
|
|
116
113
|
|
|
117
|
-
```
|
|
118
|
-
return semaphore.canAcquire([key])
|
|
114
|
+
```ts
|
|
115
|
+
return semaphore.canAcquire([key])
|
|
116
|
+
? await semaphore.request(fn, [options])
|
|
117
|
+
: null;
|
|
119
118
|
```
|
|
120
119
|
|
|
121
|
-
This is useful in scenarios where only one instance of a function block should
|
|
122
|
-
run while discarding additional attempts. For example, handling repeated button
|
|
123
|
-
clicks.
|
|
120
|
+
This is useful in scenarios where only one instance of a function block should run while discarding additional attempts. For example, handling repeated button clicks.
|
|
124
121
|
|
|
125
122
|
## Example 1
|
|
126
123
|
|
|
127
|
-
```
|
|
124
|
+
```ts
|
|
128
125
|
import { Semaphore } from "@chriscdn/promise-semaphore";
|
|
129
126
|
const semaphore = new Semaphore();
|
|
130
127
|
|
|
@@ -161,7 +158,7 @@ await semaphore.request(() => {
|
|
|
161
158
|
|
|
162
159
|
Consider an asynchronous function that downloads a file and saves it to disk:
|
|
163
160
|
|
|
164
|
-
```
|
|
161
|
+
```ts
|
|
165
162
|
const downloadAndSave = async (url) => {
|
|
166
163
|
const filePath = urlToFilePath(url);
|
|
167
164
|
|
|
@@ -175,14 +172,11 @@ const downloadAndSave = async (url) => {
|
|
|
175
172
|
};
|
|
176
173
|
```
|
|
177
174
|
|
|
178
|
-
This approach works as expected until `downloadAndSave()` is called multiple
|
|
179
|
-
times with the same `url` in quick succession. Without control, it could
|
|
180
|
-
initiate simultaneous downloads that attempt to write to the same file at the
|
|
181
|
-
same time.
|
|
175
|
+
This approach works as expected until `downloadAndSave()` is called multiple times in quick succession with the same `url`. Without control, it could initiate simultaneous downloads that attempt to write to the same file at the same time.
|
|
182
176
|
|
|
183
177
|
This issue can be resolved by using a `Semaphore` with the `key` parameter:
|
|
184
178
|
|
|
185
|
-
```
|
|
179
|
+
```ts
|
|
186
180
|
import { Semaphore } from "@chriscdn/promise-semaphore";
|
|
187
181
|
const semaphore = new Semaphore();
|
|
188
182
|
|
|
@@ -208,9 +202,9 @@ const downloadAndSave = async (url) => {
|
|
|
208
202
|
};
|
|
209
203
|
```
|
|
210
204
|
|
|
211
|
-
The same outcome can be achieved using the `request` function:
|
|
205
|
+
The same outcome can be achieved by using the `request` function:
|
|
212
206
|
|
|
213
|
-
```
|
|
207
|
+
```ts
|
|
214
208
|
const downloadAndSave = (url) => {
|
|
215
209
|
return semaphore.request(async () => {
|
|
216
210
|
const filePath = urlToFilePath(url);
|
|
@@ -227,13 +221,9 @@ const downloadAndSave = (url) => {
|
|
|
227
221
|
|
|
228
222
|
## API - GroupSemaphore
|
|
229
223
|
|
|
230
|
-
The `GroupSemaphore` class manages a semaphore for different
|
|
231
|
-
group is identified by a key, and the semaphore ensures that only one group can
|
|
232
|
-
run its tasks at a time. The tasks within a group can run concurrently.
|
|
224
|
+
The `GroupSemaphore` class manages a semaphore for different _groups_ of tasks. A group is identified by a key, and the semaphore ensures that only one group can run its tasks at a time. The tasks within a group can run concurrently.
|
|
233
225
|
|
|
234
|
-
The `GroupSemaphore` class exposes `acquire` and `release` methods, which have
|
|
235
|
-
the same interface as `Semaphore`. The only difference is that the `key`
|
|
236
|
-
parameter is required.
|
|
226
|
+
The `GroupSemaphore` class exposes `acquire` and `release` methods, which have the same interface as `Semaphore`. The only difference is that the `key` parameter is required.
|
|
237
227
|
|
|
238
228
|
### Example
|
|
239
229
|
|
|
@@ -263,11 +253,7 @@ const RunB = async () => {
|
|
|
263
253
|
};
|
|
264
254
|
```
|
|
265
255
|
|
|
266
|
-
This setup allows `RunA` to be called multiple times, and will run concurrently.
|
|
267
|
-
However, calling `RunB` will wait until all `GroupA` tasks are completed before
|
|
268
|
-
acquiring the lock for `GroupB`. As soon as `GroupB` acquires the lock, any
|
|
269
|
-
subsequent calls to `RunA` will wait until `GroupB` releases the lock before it
|
|
270
|
-
executes.
|
|
256
|
+
This setup allows `RunA` to be called multiple times, and will run concurrently. However, calling `RunB` will wait until all `GroupA` tasks are completed before acquiring the lock for `GroupB`. As soon as `GroupB` acquires the lock, any subsequent calls to `RunA` will wait until `GroupB` releases the lock before it executes.
|
|
271
257
|
|
|
272
258
|
## License
|
|
273
259
|
|
|
@@ -1,2 +1,2 @@
|
|
|
1
|
-
function e(e){var t=function(e){if("object"!=typeof e||!e)return e;var t=e[Symbol.toPrimitive];if(void 0!==t){var
|
|
1
|
+
function e(e){var t=function(e){if("object"!=typeof e||!e)return e;var t=e[Symbol.toPrimitive];if(void 0!==t){var r=t.call(e,"string");if("object"!=typeof r)return r;throw new TypeError("@@toPrimitive must return a primitive value.")}return String(e)}(e);return"symbol"==typeof t?t:t+""}var t="_default",r=function(e){return["string","number"].includes(typeof e)},n=function(e){var n;return null!=(n=r(e)?e:e.key)?n:t},i=/*#__PURE__*/function(){function t(e){this.queue=void 0,this.maxConcurrent=void 0,this.count=void 0,this.queue=[],this.maxConcurrent=e,this.count=0}var r,n,i=t.prototype;return i.incrementCount=function(){this.count++},i.decrementCount=function(){this.count--},i.acquire=function(e){var t=this;return this.canAcquire?(this.incrementCount(),Promise.resolve()):new Promise(function(r){t.queue.push({resolve:r,priority:e}),t.queue.sort(function(e,t){return t.priority-e.priority})})},i.release=function(){var e=this.queue.shift();e?setTimeout(e.resolve,0):this.decrementCount()},r=t,(n=[{key:"canAcquire",get:function(){return this.count<this.maxConcurrent}}])&&function(t,r){for(var n=0;n<r.length;n++){var i=r[n];i.enumerable=i.enumerable||!1,i.configurable=!0,"value"in i&&(i.writable=!0),Object.defineProperty(t,e(i.key),i)}}(r.prototype,n),Object.defineProperty(r,"prototype",{writable:!1}),r}(),o=/*#__PURE__*/function(){function e(e){if(void 0===e&&(e=1),this.semaphoreInstances=void 0,this.maxConcurrent=void 0,this.semaphoreInstances={},this.maxConcurrent=e,e<1)throw new Error("The maxConcurrent must be 1 or greater.")}var o=e.prototype;return o.hasSemaphoreInstance=function(e){return void 0===e&&(e=t),Boolean(this.semaphoreInstances[e])},o.getSemaphoreInstance=function(e){return void 0===e&&(e=t),this.hasSemaphoreInstance(e)||(this.semaphoreInstances[e]=new i(this.maxConcurrent)),this.semaphoreInstances[e]},o.tidy=function(e){void 0===e&&(e=t),this.hasSemaphoreInstance(e)&&0===this.getSemaphoreInstance(e).count&&delete this.semaphoreInstances[e]},o.canAcquire=function(e){void 0===e&&(e=t);var r=n(e);return!this.hasSemaphoreInstance(r)||this.getSemaphoreInstance(r).canAcquire},o.acquire=function(e){void 0===e&&(e=t);var i,o,s=n(e),u=null!=(o=r(i=e)?0:i.priority)?o:0;return this.getSemaphoreInstance(s).acquire(u)},o.release=function(e){void 0===e&&(e=t);var r=n(e);this.getSemaphoreInstance(r).release(),this.tidy(r)},o.count=function(e){void 0===e&&(e=t);var r=n(e);return this.hasSemaphoreInstance(r)?this.getSemaphoreInstance(r).count:0},o.hasTasks=function(e){return void 0===e&&(e=t),this.count(e)>0},o.request=function(e,r){void 0===r&&(r=t);try{var n=this;return Promise.resolve(function(t,i){try{var o=Promise.resolve(n.acquire(r)).then(function(){return Promise.resolve(e())})}catch(e){return i(!0,e)}return o&&o.then?o.then(i.bind(null,!1),i.bind(null,!0)):i(!1,o)}(0,function(e,t){if(n.release(r),e)throw t;return t}))}catch(e){return Promise.reject(e)}},o.requestIfAvailable=function(e,r){void 0===r&&(r=t);try{return this.canAcquire(r)?Promise.resolve(this.request(e,r)):Promise.resolve(null)}catch(e){return Promise.reject(e)}},e}();exports.GroupSemaphore=/*#__PURE__*/function(){function e(){this._semaphore=new o,this._activeCounts={},this._groupWaiters={}}var t=e.prototype;return t.acquire=function(e){try{var t,r,n=this,i=null!=(t=n._activeCounts[e])?t:0;n._activeCounts[e]=i+1;var o=null!=(r=n._groupWaiters[e])?r:n._semaphore.acquire();return n._groupWaiters[e]=o,Promise.resolve(o).then(function(){})}catch(e){return Promise.reject(e)}},t.release=function(e){var t=this._activeCounts[e];1===t?(this._semaphore.release(),delete this._activeCounts[e],delete this._groupWaiters[e]):this._activeCounts[e]=t-1},e}(),exports.Semaphore=o;
|
|
2
2
|
//# sourceMappingURL=promise-semaphore.cjs.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"promise-semaphore.cjs","sources":["../src/semaphore.ts","../src/group-semaphore.ts"],"sourcesContent":["class SemaphoreItem {\n private queue: Function[];\n private maxConcurrent: number;\n\n /**\n * The number of locks.\n */\n public count: number;\n\n constructor(maxConcurrent: number) {\n this.queue = [];\n this.maxConcurrent = maxConcurrent;\n this.count = 0;\n }\n\n get canAcquire(): boolean {\n return this.count < this.maxConcurrent;\n }\n\n private incrementCount() {\n this.count++;\n }\n\n private decrementCount() {\n this.count--;\n }\n\n acquire(): Promise<void> {\n if (this.canAcquire) {\n this.incrementCount();\n return Promise.resolve();\n } else {\n return new Promise((resolve) => this.queue.push(resolve));\n }\n }\n\n release(): void {\n const resolveFunc = this.queue.shift();\n\n if (resolveFunc) {\n // Give the micro task queue a small break instead of calling resolveFunc() directly\n setTimeout(resolveFunc, 0);\n } else {\n this.decrementCount();\n }\n }\n}\n\nconst defaultKey = \"_default\";\n\nclass Semaphore {\n private semaphoreInstances: Record<string | number, SemaphoreItem>;\n private maxConcurrent: number;\n\n /**\n * @param {number} [maxConcurrent] The maximum number of concurrent locks.\n */\n constructor(maxConcurrent: number = 1) {\n this.semaphoreInstances = {};\n this.maxConcurrent = maxConcurrent;\n }\n\n private hasSemaphoreInstance(key: string | number = defaultKey) {\n return Boolean(this.semaphoreInstances[key]);\n }\n\n private getSemaphoreInstance(key: string | number = defaultKey) {\n if (!this.hasSemaphoreInstance(key)) {\n this.semaphoreInstances[key] = new SemaphoreItem(\n this.maxConcurrent,\n );\n }\n return this.semaphoreInstances[key];\n }\n\n /**\n * @param {string | number} [key]- Optional, the semaphore key.\n */\n private tidy(key: string | number = defaultKey): void {\n if (\n this.hasSemaphoreInstance(key) &&\n this.getSemaphoreInstance(key).count === 0\n ) {\n delete this.semaphoreInstances[key];\n }\n }\n\n /**\n * A synchronous function to determine whether a lock can be acquired.\n *\n * @param {string | number} [key]- Optional, the semaphore key.\n * @returns {boolean} Returns true if the lock on `key` can be acquired, false\n * otherwise.\n */\n canAcquire(key: string | number = defaultKey): boolean {\n return !this.hasSemaphoreInstance(key) ||\n this.getSemaphoreInstance(key).canAcquire;\n }\n\n /**\n * @param {string | number} [key]- Optional, the semaphore key.\n */\n acquire(key: string | number = defaultKey) {\n return this.getSemaphoreInstance(key).acquire();\n }\n\n /**\n * @param {string | number} [key]- Optional, the semaphore key.\n */\n release(key: string | number = defaultKey): void {\n this.getSemaphoreInstance(key).release();\n this.tidy(key);\n }\n\n /**\n * The number of active locks. Will always be less or equal to `max`.\n *\n * @param {string | number} [key]- Optional, the semaphore key.\n */\n count(key: string | number = defaultKey): number {\n if (this.hasSemaphoreInstance(key)) {\n return this.getSemaphoreInstance(key).count;\n } else {\n return 0;\n }\n }\n\n /**\n * @param {string | number} [key]- Optional, the semaphore key.\n * @returns {boolean} True if the semaphore and key has locks, false otherwise.\n */\n hasTasks(key: string | number = defaultKey): boolean {\n return this.count(key) > 0;\n }\n\n /**\n * @param {Function<T>} fn The function to execute.\n * @param {string | number} [key]- Optional, the semaphore key.\n * @returns {Promise<T>}\n */\n async request<T>(\n fn: Function,\n key: string | number = defaultKey,\n ): Promise<T> {\n try {\n await this.acquire(key);\n return await fn();\n } finally {\n this.release(key);\n }\n }\n\n /**\n * Asynchronously executes `fn` if a lock can be immediately acquired.\n * Otherwise, returns null.\n *\n * @param {Function<T>} fn The function to execute.\n * @param {string | number} [key]- Optional, the semaphore key.\n * @returns {Promise<T>}\n */\n async requestIfAvailable<T>(\n fn: Function,\n key: string | number = defaultKey,\n ): Promise<T | null> {\n if (this.canAcquire(key)) {\n return this.request(fn, key);\n } else {\n return null;\n }\n }\n}\n\nexport { Semaphore };\n","import { Semaphore } from \"./semaphore\";\n\n/**\n * GroupSemaphore manages a shared semaphore for different groups of tasks. Each\n * group is identified by a unique key, and the semaphore ensures only one group\n * can run its tasks concurrently.\n *\n * - acquire(key): Increments the active count for the given group. If it's the\n * first task for the group (active count is 0), it acquires the global\n * semaphore, ensuring only one group's tasks can proceed at a time.\n * Subsequent calls in the group increment the count and are permitted to run.\n * - release(key): Decrements the active count for the group. If the last task\n * for that group is released, it releases the global semaphore, allowing\n * other groups to proceed.\n *\n * This ensures that only one group can execute concurrently, but multiple tasks\n * within the same group can run as long as no other tasks from different groups\n * are active.\n */\nclass GroupSemaphore {\n private _semaphore = new Semaphore();\n private _activeCounts: Record<string, number> = {};\n private _groupWaiters: Record<string, Promise<void>> = {};\n\n async acquire(key: string) {\n const activeCount = this._activeCounts[key] ?? 0;\n this._activeCounts[key] = activeCount + 1;\n const waiter = this._groupWaiters[key] ?? this._semaphore.acquire();\n this._groupWaiters[key] = waiter;\n await waiter;\n }\n\n release(key: string) {\n const activeCount = this._activeCounts[key];\n\n if (activeCount === 1) {\n this._semaphore.release();\n delete this._activeCounts[key];\n delete this._groupWaiters[key];\n } else {\n this._activeCounts[key] = activeCount - 1;\n }\n }\n}\n\nexport { GroupSemaphore };\n"],"names":["SemaphoreItem","maxConcurrent","queue","this","count","_proto","prototype","incrementCount","decrementCount","acquire","_this","canAcquire","Promise","resolve","push","release","resolveFunc","shift","setTimeout","key","get","defaultKey","Semaphore","semaphoreInstances","_proto2","hasSemaphoreInstance","Boolean","getSemaphoreInstance","tidy","hasTasks","request","fn","_this2","then","_finallyRethrows","_wasThrown","_result","e","reject","requestIfAvailable","GroupSemaphore","_semaphore","_activeCounts","_groupWaiters","_this$_activeCounts$k","_this$_groupWaiters$k","activeCount","waiter"],"mappings":"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"}
|
|
1
|
+
{"version":3,"file":"promise-semaphore.cjs","sources":["../src/semaphore.ts","../src/group-semaphore.ts"],"sourcesContent":["const defaultKey = \"_default\";\n\ntype KeyPrimitive = string | number;\n\ntype Key = KeyPrimitive | { key?: KeyPrimitive };\ntype KeyOptions = Key & { priority?: number };\n\nconst _isPrimitiveKey = (item: Key): item is KeyPrimitive =>\n [\"string\", \"number\"].includes(typeof item);\n\nconst resolveKey = (item: Key): KeyPrimitive =>\n (_isPrimitiveKey(item) ? item : item.key) ?? defaultKey;\n\nconst resolvePriority = (item: KeyOptions) =>\n (_isPrimitiveKey(item) ? 0 : item.priority) ?? 0;\n\nclass SemaphoreItem {\n private queue: Array<{\n resolve: Function;\n priority: number;\n }>;\n private maxConcurrent: number;\n\n /**\n * The number of locks.\n */\n public count: number;\n\n constructor(maxConcurrent: number) {\n this.queue = [];\n this.maxConcurrent = maxConcurrent;\n this.count = 0;\n }\n\n get canAcquire(): boolean {\n return this.count < this.maxConcurrent;\n }\n\n private incrementCount() {\n this.count++;\n }\n\n private decrementCount() {\n this.count--;\n }\n\n acquire(priority: number): Promise<void> {\n if (this.canAcquire) {\n this.incrementCount();\n return Promise.resolve();\n } else {\n return new Promise((resolve) => {\n this.queue.push({ resolve, priority });\n this.queue.sort((a, b) => b.priority - a.priority);\n });\n }\n }\n\n release(): void {\n const resolveFunc = this.queue.shift();\n\n if (resolveFunc) {\n // Give the micro task queue a small break instead of calling resolveFunc() directly\n setTimeout(resolveFunc.resolve, 0);\n } else {\n this.decrementCount();\n }\n }\n}\n\nclass Semaphore {\n private semaphoreInstances: Record<string | number, SemaphoreItem>;\n private maxConcurrent: number;\n\n /**\n * @param {number} [maxConcurrent] The maximum number of concurrent locks.\n */\n constructor(maxConcurrent: number = 1) {\n this.semaphoreInstances = {};\n this.maxConcurrent = maxConcurrent;\n\n if (maxConcurrent < 1) {\n throw new Error(\"The maxConcurrent must be 1 or greater.\");\n }\n }\n\n private hasSemaphoreInstance(key: KeyPrimitive = defaultKey) {\n return Boolean(this.semaphoreInstances[key]);\n }\n\n private getSemaphoreInstance(key: KeyPrimitive = defaultKey) {\n if (!this.hasSemaphoreInstance(key)) {\n this.semaphoreInstances[key] = new SemaphoreItem(\n this.maxConcurrent,\n );\n }\n return this.semaphoreInstances[key];\n }\n\n /**\n * @param {string | number} [key]- Optional, the semaphore key.\n */\n private tidy(key: KeyPrimitive = defaultKey): void {\n if (\n this.hasSemaphoreInstance(key) &&\n this.getSemaphoreInstance(key).count === 0\n ) {\n delete this.semaphoreInstances[key];\n }\n }\n\n /**\n * A synchronous function to determine whether a lock can be acquired.\n *\n * @param {string | number} [key]- Optional, the semaphore key.\n * @returns {boolean} Returns true if the lock on `key` can be acquired, false\n * otherwise.\n */\n canAcquire(key: Key = defaultKey): boolean {\n const _key = resolveKey(key);\n\n return !this.hasSemaphoreInstance(_key) ||\n this.getSemaphoreInstance(_key).canAcquire;\n }\n\n /**\n * @param {string | number} [key]- Optional, the semaphore key.\n */\n acquire(key: KeyOptions = defaultKey) {\n const _key = resolveKey(key);\n const _priority = resolvePriority(key);\n\n return this.getSemaphoreInstance(_key).acquire(_priority);\n }\n\n /**\n * @param {string | number} [key]- Optional, the semaphore key.\n */\n release(key: Key = defaultKey): void {\n const _key = resolveKey(key);\n\n this.getSemaphoreInstance(_key).release();\n this.tidy(_key);\n }\n\n /**\n * The number of active locks. Will always be less or equal to `max`.\n *\n * @param {string | number} [key]- Optional, the semaphore key.\n */\n count(key: Key = defaultKey): number {\n const _key = resolveKey(key);\n\n return (this.hasSemaphoreInstance(_key))\n ? this.getSemaphoreInstance(_key).count\n : 0;\n }\n\n /**\n * @param {string | number} [key]- Optional, the semaphore key.\n * @returns {boolean} True if the semaphore and key has locks, false otherwise.\n */\n hasTasks(key: Key = defaultKey): boolean {\n return this.count(key) > 0;\n }\n\n /**\n * @param {Function<T>} fn The function to execute.\n * @param {string | number} [key]- Optional, the semaphore key.\n * @returns {Promise<T>}\n */\n async request<T>(\n fn: Function,\n key: KeyOptions = defaultKey,\n ): Promise<T> {\n try {\n await this.acquire(key);\n return await fn();\n } finally {\n this.release(key);\n }\n }\n\n /**\n * Asynchronously executes `fn` if a lock can be immediately acquired.\n * Otherwise, returns null.\n *\n * @param {Function<T>} fn The function to execute.\n * @param {string | number} [key]- Optional, the semaphore key.\n * @returns {Promise<T>}\n */\n async requestIfAvailable<T>(\n fn: Function,\n key: KeyOptions = defaultKey,\n ): Promise<T | null> {\n if (this.canAcquire(key)) {\n return this.request(fn, key);\n } else {\n return null;\n }\n }\n}\n\nexport { Semaphore };\n","import { Semaphore } from \"./semaphore\";\n\n/**\n * GroupSemaphore manages a shared semaphore for different groups of tasks. Each\n * group is identified by a unique key, and the semaphore ensures only one group\n * can run its tasks concurrently.\n *\n * - acquire(key): Increments the active count for the given group. If it's the\n * first task for the group (active count is 0), it acquires the global\n * semaphore, ensuring only one group's tasks can proceed at a time.\n * Subsequent calls in the group increment the count and are permitted to run.\n * - release(key): Decrements the active count for the group. If the last task\n * for that group is released, it releases the global semaphore, allowing\n * other groups to proceed.\n *\n * This ensures that only one group can execute concurrently, but multiple tasks\n * within the same group can run as long as no other tasks from different groups\n * are active.\n */\nclass GroupSemaphore {\n private _semaphore = new Semaphore();\n private _activeCounts: Record<string, number> = {};\n private _groupWaiters: Record<string, Promise<void>> = {};\n\n async acquire(key: string) {\n const activeCount = this._activeCounts[key] ?? 0;\n this._activeCounts[key] = activeCount + 1;\n const waiter = this._groupWaiters[key] ?? this._semaphore.acquire();\n this._groupWaiters[key] = waiter;\n await waiter;\n }\n\n release(key: string) {\n const activeCount = this._activeCounts[key];\n\n if (activeCount === 1) {\n this._semaphore.release();\n delete this._activeCounts[key];\n delete this._groupWaiters[key];\n } else {\n this._activeCounts[key] = activeCount - 1;\n }\n }\n}\n\nexport { GroupSemaphore };\n"],"names":["defaultKey","_isPrimitiveKey","item","includes","resolveKey","_ref","key","SemaphoreItem","maxConcurrent","queue","this","count","_proto","prototype","_createClass","incrementCount","decrementCount","acquire","priority","_this","canAcquire","Promise","resolve","push","sort","a","b","release","resolveFunc","shift","setTimeout","get","Semaphore","semaphoreInstances","Error","_proto2","hasSemaphoreInstance","Boolean","getSemaphoreInstance","tidy","_key","_ref2","_priority","hasTasks","request","fn","_this2","then","_finallyRethrows","_wasThrown","_result","e","reject","requestIfAvailable","GroupSemaphore","_semaphore","_activeCounts","_groupWaiters","_this$_activeCounts$k","_this$_groupWaiters$k","activeCount","waiter"],"mappings":"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"}
|
|
@@ -1,2 +1,2 @@
|
|
|
1
|
-
|
|
1
|
+
const e="_default",t=e=>["string","number"].includes(typeof e),s=s=>{var r;return null!=(r=t(s)?s:s.key)?r:e};class r{constructor(e){this.queue=void 0,this.maxConcurrent=void 0,this.count=void 0,this.queue=[],this.maxConcurrent=e,this.count=0}get canAcquire(){return this.count<this.maxConcurrent}incrementCount(){this.count++}decrementCount(){this.count--}acquire(e){return this.canAcquire?(this.incrementCount(),Promise.resolve()):new Promise(t=>{this.queue.push({resolve:t,priority:e}),this.queue.sort((e,t)=>t.priority-e.priority)})}release(){const e=this.queue.shift();e?setTimeout(e.resolve,0):this.decrementCount()}}class n{constructor(e=1){if(this.semaphoreInstances=void 0,this.maxConcurrent=void 0,this.semaphoreInstances={},this.maxConcurrent=e,e<1)throw new Error("The maxConcurrent must be 1 or greater.")}hasSemaphoreInstance(t=e){return Boolean(this.semaphoreInstances[t])}getSemaphoreInstance(t=e){return this.hasSemaphoreInstance(t)||(this.semaphoreInstances[t]=new r(this.maxConcurrent)),this.semaphoreInstances[t]}tidy(t=e){this.hasSemaphoreInstance(t)&&0===this.getSemaphoreInstance(t).count&&delete this.semaphoreInstances[t]}canAcquire(t=e){const r=s(t);return!this.hasSemaphoreInstance(r)||this.getSemaphoreInstance(r).canAcquire}acquire(r=e){const n=s(r),i=null!=(o=t(a=r)?0:a.priority)?o:0;var a,o;return this.getSemaphoreInstance(n).acquire(i)}release(t=e){const r=s(t);this.getSemaphoreInstance(r).release(),this.tidy(r)}count(t=e){const r=s(t);return this.hasSemaphoreInstance(r)?this.getSemaphoreInstance(r).count:0}hasTasks(t=e){return this.count(t)>0}async request(t,s=e){try{return await this.acquire(s),await t()}finally{this.release(s)}}async requestIfAvailable(t,s=e){return this.canAcquire(s)?this.request(t,s):null}}class i{constructor(){this._semaphore=new n,this._activeCounts={},this._groupWaiters={}}async acquire(e){var t,s;const r=null!=(t=this._activeCounts[e])?t:0;this._activeCounts[e]=r+1;const n=null!=(s=this._groupWaiters[e])?s:this._semaphore.acquire();this._groupWaiters[e]=n,await n}release(e){const t=this._activeCounts[e];1===t?(this._semaphore.release(),delete this._activeCounts[e],delete this._groupWaiters[e]):this._activeCounts[e]=t-1}}export{i as GroupSemaphore,n as Semaphore};
|
|
2
2
|
//# sourceMappingURL=promise-semaphore.modern.js.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"promise-semaphore.modern.js","sources":["../src/semaphore.ts","../src/group-semaphore.ts"],"sourcesContent":["class SemaphoreItem {\n private queue: Function[];\n private maxConcurrent: number;\n\n /**\n * The number of locks.\n */\n public count: number;\n\n constructor(maxConcurrent: number) {\n this.queue = [];\n this.maxConcurrent = maxConcurrent;\n this.count = 0;\n }\n\n get canAcquire(): boolean {\n return this.count < this.maxConcurrent;\n }\n\n private incrementCount() {\n this.count++;\n }\n\n private decrementCount() {\n this.count--;\n }\n\n acquire(): Promise<void> {\n if (this.canAcquire) {\n this.incrementCount();\n return Promise.resolve();\n } else {\n return new Promise((resolve) => this.queue.push(resolve));\n }\n }\n\n release(): void {\n const resolveFunc = this.queue.shift();\n\n if (resolveFunc) {\n // Give the micro task queue a small break instead of calling resolveFunc() directly\n setTimeout(resolveFunc, 0);\n } else {\n this.decrementCount();\n }\n }\n}\n\nconst defaultKey = \"_default\";\n\nclass Semaphore {\n private semaphoreInstances: Record<string | number, SemaphoreItem>;\n private maxConcurrent: number;\n\n /**\n * @param {number} [maxConcurrent] The maximum number of concurrent locks.\n */\n constructor(maxConcurrent: number = 1) {\n this.semaphoreInstances = {};\n this.maxConcurrent = maxConcurrent;\n }\n\n private hasSemaphoreInstance(key: string | number = defaultKey) {\n return Boolean(this.semaphoreInstances[key]);\n }\n\n private getSemaphoreInstance(key: string | number = defaultKey) {\n if (!this.hasSemaphoreInstance(key)) {\n this.semaphoreInstances[key] = new SemaphoreItem(\n this.maxConcurrent,\n );\n }\n return this.semaphoreInstances[key];\n }\n\n /**\n * @param {string | number} [key]- Optional, the semaphore key.\n */\n private tidy(key: string | number = defaultKey): void {\n if (\n this.hasSemaphoreInstance(key) &&\n this.getSemaphoreInstance(key).count === 0\n ) {\n delete this.semaphoreInstances[key];\n }\n }\n\n /**\n * A synchronous function to determine whether a lock can be acquired.\n *\n * @param {string | number} [key]- Optional, the semaphore key.\n * @returns {boolean} Returns true if the lock on `key` can be acquired, false\n * otherwise.\n */\n canAcquire(key: string | number = defaultKey): boolean {\n return !this.hasSemaphoreInstance(key) ||\n this.getSemaphoreInstance(key).canAcquire;\n }\n\n /**\n * @param {string | number} [key]- Optional, the semaphore key.\n */\n acquire(key: string | number = defaultKey) {\n return this.getSemaphoreInstance(key).acquire();\n }\n\n /**\n * @param {string | number} [key]- Optional, the semaphore key.\n */\n release(key: string | number = defaultKey): void {\n this.getSemaphoreInstance(key).release();\n this.tidy(key);\n }\n\n /**\n * The number of active locks. Will always be less or equal to `max`.\n *\n * @param {string | number} [key]- Optional, the semaphore key.\n */\n count(key: string | number = defaultKey): number {\n if (this.hasSemaphoreInstance(key)) {\n return this.getSemaphoreInstance(key).count;\n } else {\n return 0;\n }\n }\n\n /**\n * @param {string | number} [key]- Optional, the semaphore key.\n * @returns {boolean} True if the semaphore and key has locks, false otherwise.\n */\n hasTasks(key: string | number = defaultKey): boolean {\n return this.count(key) > 0;\n }\n\n /**\n * @param {Function<T>} fn The function to execute.\n * @param {string | number} [key]- Optional, the semaphore key.\n * @returns {Promise<T>}\n */\n async request<T>(\n fn: Function,\n key: string | number = defaultKey,\n ): Promise<T> {\n try {\n await this.acquire(key);\n return await fn();\n } finally {\n this.release(key);\n }\n }\n\n /**\n * Asynchronously executes `fn` if a lock can be immediately acquired.\n * Otherwise, returns null.\n *\n * @param {Function<T>} fn The function to execute.\n * @param {string | number} [key]- Optional, the semaphore key.\n * @returns {Promise<T>}\n */\n async requestIfAvailable<T>(\n fn: Function,\n key: string | number = defaultKey,\n ): Promise<T | null> {\n if (this.canAcquire(key)) {\n return this.request(fn, key);\n } else {\n return null;\n }\n }\n}\n\nexport { Semaphore };\n","import { Semaphore } from \"./semaphore\";\n\n/**\n * GroupSemaphore manages a shared semaphore for different groups of tasks. Each\n * group is identified by a unique key, and the semaphore ensures only one group\n * can run its tasks concurrently.\n *\n * - acquire(key): Increments the active count for the given group. If it's the\n * first task for the group (active count is 0), it acquires the global\n * semaphore, ensuring only one group's tasks can proceed at a time.\n * Subsequent calls in the group increment the count and are permitted to run.\n * - release(key): Decrements the active count for the group. If the last task\n * for that group is released, it releases the global semaphore, allowing\n * other groups to proceed.\n *\n * This ensures that only one group can execute concurrently, but multiple tasks\n * within the same group can run as long as no other tasks from different groups\n * are active.\n */\nclass GroupSemaphore {\n private _semaphore = new Semaphore();\n private _activeCounts: Record<string, number> = {};\n private _groupWaiters: Record<string, Promise<void>> = {};\n\n async acquire(key: string) {\n const activeCount = this._activeCounts[key] ?? 0;\n this._activeCounts[key] = activeCount + 1;\n const waiter = this._groupWaiters[key] ?? this._semaphore.acquire();\n this._groupWaiters[key] = waiter;\n await waiter;\n }\n\n release(key: string) {\n const activeCount = this._activeCounts[key];\n\n if (activeCount === 1) {\n this._semaphore.release();\n delete this._activeCounts[key];\n delete this._groupWaiters[key];\n } else {\n this._activeCounts[key] = activeCount - 1;\n }\n }\n}\n\nexport { GroupSemaphore };\n"],"names":["SemaphoreItem","constructor","maxConcurrent","this","queue","count","canAcquire","incrementCount","decrementCount","acquire","Promise","resolve","push","release","resolveFunc","shift","setTimeout","defaultKey","Semaphore","semaphoreInstances","hasSemaphoreInstance","key","Boolean","getSemaphoreInstance","tidy","hasTasks","request","fn","requestIfAvailable","GroupSemaphore","_semaphore","_activeCounts","_groupWaiters","_this$_activeCounts$k","_this$_groupWaiters$k","activeCount","waiter"],"mappings":"AAAA,MAAMA,EASFC,WAAAA,CAAYC,GAAqBC,KARzBC,WAAK,EAAAD,KACLD,mBAAa,EAAAC,KAKdE,WAGH,EAAAF,KAAKC,MAAQ,GACbD,KAAKD,cAAgBA,EACrBC,KAAKE,MAAQ,CACjB,CAEA,cAAIC,GACA,OAAOH,KAAKE,MAAQF,KAAKD,aAC7B,CAEQK,cAAAA,GACJJ,KAAKE,OACT,CAEQG,cAAAA,GACJL,KAAKE,OACT,CAEAI,OAAAA,GACI,OAAIN,KAAKG,YACLH,KAAKI,iBACEG,QAAQC,WAER,IAAID,QAASC,GAAYR,KAAKC,MAAMQ,KAAKD,GAExD,CAEAE,OAAAA,GACI,MAAMC,EAAcX,KAAKC,MAAMW,QAE3BD,EAEAE,WAAWF,EAAa,GAExBX,KAAKK,gBAEb,EAGJ,MAAMS,EAAa,WAEnB,MAAMC,EAOFjB,WAAAA,CAAYC,EAAwB,GAACC,KAN7BgB,wBAAkB,EAAAhB,KAClBD,mBAAa,EAMjBC,KAAKgB,mBAAqB,CAAA,EAC1BhB,KAAKD,cAAgBA,CACzB,CAEQkB,oBAAAA,CAAqBC,EAAuBJ,GAChD,OAAOK,QAAQnB,KAAKgB,mBAAmBE,GAC3C,CAEQE,oBAAAA,CAAqBF,EAAuBJ,GAMhD,OALKd,KAAKiB,qBAAqBC,KAC3BlB,KAAKgB,mBAAmBE,GAAO,IAAIrB,EAC/BG,KAAKD,gBAGFC,KAACgB,mBAAmBE,EACnC,CAKQG,IAAAA,CAAKH,EAAuBJ,GAE5Bd,KAAKiB,qBAAqBC,IACe,IAAzClB,KAAKoB,qBAAqBF,GAAKhB,cAExBF,KAAKgB,mBAAmBE,EAEvC,CASAf,UAAAA,CAAWe,EAAuBJ,GAC9B,OAAQd,KAAKiB,qBAAqBC,IAC9BlB,KAAKoB,qBAAqBF,GAAKf,UACvC,CAKAG,OAAAA,CAAQY,EAAuBJ,GAC3B,OAAWd,KAACoB,qBAAqBF,GAAKZ,SAC1C,CAKAI,OAAAA,CAAQQ,EAAuBJ,GAC3Bd,KAAKoB,qBAAqBF,GAAKR,UAC/BV,KAAKqB,KAAKH,EACd,CAOAhB,KAAAA,CAAMgB,EAAuBJ,GACzB,OAAId,KAAKiB,qBAAqBC,GACnBlB,KAAKoB,qBAAqBF,GAAKhB,MAGzC,CACL,CAMAoB,QAAAA,CAASJ,EAAuBJ,GAC5B,OAAWd,KAACE,MAAMgB,GAAO,CAC7B,CAOA,aAAMK,CACFC,EACAN,EAAuBJ,GAEvB,IAEI,aADUd,KAACM,QAAQY,SACNM,GAChB,CAAA,QACGxB,KAAKU,QAAQQ,EAChB,CACL,CAUA,wBAAMO,CACFD,EACAN,EAAuBJ,GAEvB,OAAId,KAAKG,WAAWe,GACLlB,KAACuB,QAAQC,EAAIN,GAEjB,IAEf,ECtJJ,MAAMQ,EAAc5B,WAAAA,GAAAE,KACR2B,WAAa,IAAIZ,OACjBa,cAAwC,CAAE,OAC1CC,cAA+C,CAAA,CAAE,CAEzD,aAAMvB,CAAQY,GAAW,IAAAY,EAAAC,EACrB,MAAMC,EAAqCF,OAA1BA,EAAG9B,KAAK4B,cAAcV,IAAIY,EAAI,EAC/C9B,KAAK4B,cAAcV,GAAOc,EAAc,EACxC,MAAMC,EAAgC,OAA1BF,EAAG/B,KAAK6B,cAAcX,IAAIa,EAAI/B,KAAK2B,WAAWrB,UAC1DN,KAAK6B,cAAcX,GAAOe,QACpBA,CACV,CAEAvB,OAAAA,CAAQQ,GACJ,MAAMc,EAAchC,KAAK4B,cAAcV,GAEnB,IAAhBc,GACAhC,KAAK2B,WAAWjB,iBACTV,KAAK4B,cAAcV,UACnBlB,KAAK6B,cAAcX,IAE1BlB,KAAK4B,cAAcV,GAAOc,EAAc,CAEhD"}
|
|
1
|
+
{"version":3,"file":"promise-semaphore.modern.js","sources":["../src/semaphore.ts","../src/group-semaphore.ts"],"sourcesContent":["const defaultKey = \"_default\";\n\ntype KeyPrimitive = string | number;\n\ntype Key = KeyPrimitive | { key?: KeyPrimitive };\ntype KeyOptions = Key & { priority?: number };\n\nconst _isPrimitiveKey = (item: Key): item is KeyPrimitive =>\n [\"string\", \"number\"].includes(typeof item);\n\nconst resolveKey = (item: Key): KeyPrimitive =>\n (_isPrimitiveKey(item) ? item : item.key) ?? defaultKey;\n\nconst resolvePriority = (item: KeyOptions) =>\n (_isPrimitiveKey(item) ? 0 : item.priority) ?? 0;\n\nclass SemaphoreItem {\n private queue: Array<{\n resolve: Function;\n priority: number;\n }>;\n private maxConcurrent: number;\n\n /**\n * The number of locks.\n */\n public count: number;\n\n constructor(maxConcurrent: number) {\n this.queue = [];\n this.maxConcurrent = maxConcurrent;\n this.count = 0;\n }\n\n get canAcquire(): boolean {\n return this.count < this.maxConcurrent;\n }\n\n private incrementCount() {\n this.count++;\n }\n\n private decrementCount() {\n this.count--;\n }\n\n acquire(priority: number): Promise<void> {\n if (this.canAcquire) {\n this.incrementCount();\n return Promise.resolve();\n } else {\n return new Promise((resolve) => {\n this.queue.push({ resolve, priority });\n this.queue.sort((a, b) => b.priority - a.priority);\n });\n }\n }\n\n release(): void {\n const resolveFunc = this.queue.shift();\n\n if (resolveFunc) {\n // Give the micro task queue a small break instead of calling resolveFunc() directly\n setTimeout(resolveFunc.resolve, 0);\n } else {\n this.decrementCount();\n }\n }\n}\n\nclass Semaphore {\n private semaphoreInstances: Record<string | number, SemaphoreItem>;\n private maxConcurrent: number;\n\n /**\n * @param {number} [maxConcurrent] The maximum number of concurrent locks.\n */\n constructor(maxConcurrent: number = 1) {\n this.semaphoreInstances = {};\n this.maxConcurrent = maxConcurrent;\n\n if (maxConcurrent < 1) {\n throw new Error(\"The maxConcurrent must be 1 or greater.\");\n }\n }\n\n private hasSemaphoreInstance(key: KeyPrimitive = defaultKey) {\n return Boolean(this.semaphoreInstances[key]);\n }\n\n private getSemaphoreInstance(key: KeyPrimitive = defaultKey) {\n if (!this.hasSemaphoreInstance(key)) {\n this.semaphoreInstances[key] = new SemaphoreItem(\n this.maxConcurrent,\n );\n }\n return this.semaphoreInstances[key];\n }\n\n /**\n * @param {string | number} [key]- Optional, the semaphore key.\n */\n private tidy(key: KeyPrimitive = defaultKey): void {\n if (\n this.hasSemaphoreInstance(key) &&\n this.getSemaphoreInstance(key).count === 0\n ) {\n delete this.semaphoreInstances[key];\n }\n }\n\n /**\n * A synchronous function to determine whether a lock can be acquired.\n *\n * @param {string | number} [key]- Optional, the semaphore key.\n * @returns {boolean} Returns true if the lock on `key` can be acquired, false\n * otherwise.\n */\n canAcquire(key: Key = defaultKey): boolean {\n const _key = resolveKey(key);\n\n return !this.hasSemaphoreInstance(_key) ||\n this.getSemaphoreInstance(_key).canAcquire;\n }\n\n /**\n * @param {string | number} [key]- Optional, the semaphore key.\n */\n acquire(key: KeyOptions = defaultKey) {\n const _key = resolveKey(key);\n const _priority = resolvePriority(key);\n\n return this.getSemaphoreInstance(_key).acquire(_priority);\n }\n\n /**\n * @param {string | number} [key]- Optional, the semaphore key.\n */\n release(key: Key = defaultKey): void {\n const _key = resolveKey(key);\n\n this.getSemaphoreInstance(_key).release();\n this.tidy(_key);\n }\n\n /**\n * The number of active locks. Will always be less or equal to `max`.\n *\n * @param {string | number} [key]- Optional, the semaphore key.\n */\n count(key: Key = defaultKey): number {\n const _key = resolveKey(key);\n\n return (this.hasSemaphoreInstance(_key))\n ? this.getSemaphoreInstance(_key).count\n : 0;\n }\n\n /**\n * @param {string | number} [key]- Optional, the semaphore key.\n * @returns {boolean} True if the semaphore and key has locks, false otherwise.\n */\n hasTasks(key: Key = defaultKey): boolean {\n return this.count(key) > 0;\n }\n\n /**\n * @param {Function<T>} fn The function to execute.\n * @param {string | number} [key]- Optional, the semaphore key.\n * @returns {Promise<T>}\n */\n async request<T>(\n fn: Function,\n key: KeyOptions = defaultKey,\n ): Promise<T> {\n try {\n await this.acquire(key);\n return await fn();\n } finally {\n this.release(key);\n }\n }\n\n /**\n * Asynchronously executes `fn` if a lock can be immediately acquired.\n * Otherwise, returns null.\n *\n * @param {Function<T>} fn The function to execute.\n * @param {string | number} [key]- Optional, the semaphore key.\n * @returns {Promise<T>}\n */\n async requestIfAvailable<T>(\n fn: Function,\n key: KeyOptions = defaultKey,\n ): Promise<T | null> {\n if (this.canAcquire(key)) {\n return this.request(fn, key);\n } else {\n return null;\n }\n }\n}\n\nexport { Semaphore };\n","import { Semaphore } from \"./semaphore\";\n\n/**\n * GroupSemaphore manages a shared semaphore for different groups of tasks. Each\n * group is identified by a unique key, and the semaphore ensures only one group\n * can run its tasks concurrently.\n *\n * - acquire(key): Increments the active count for the given group. If it's the\n * first task for the group (active count is 0), it acquires the global\n * semaphore, ensuring only one group's tasks can proceed at a time.\n * Subsequent calls in the group increment the count and are permitted to run.\n * - release(key): Decrements the active count for the group. If the last task\n * for that group is released, it releases the global semaphore, allowing\n * other groups to proceed.\n *\n * This ensures that only one group can execute concurrently, but multiple tasks\n * within the same group can run as long as no other tasks from different groups\n * are active.\n */\nclass GroupSemaphore {\n private _semaphore = new Semaphore();\n private _activeCounts: Record<string, number> = {};\n private _groupWaiters: Record<string, Promise<void>> = {};\n\n async acquire(key: string) {\n const activeCount = this._activeCounts[key] ?? 0;\n this._activeCounts[key] = activeCount + 1;\n const waiter = this._groupWaiters[key] ?? this._semaphore.acquire();\n this._groupWaiters[key] = waiter;\n await waiter;\n }\n\n release(key: string) {\n const activeCount = this._activeCounts[key];\n\n if (activeCount === 1) {\n this._semaphore.release();\n delete this._activeCounts[key];\n delete this._groupWaiters[key];\n } else {\n this._activeCounts[key] = activeCount - 1;\n }\n }\n}\n\nexport { GroupSemaphore };\n"],"names":["defaultKey","_isPrimitiveKey","item","includes","resolveKey","_ref","key","SemaphoreItem","constructor","maxConcurrent","queue","this","count","canAcquire","incrementCount","decrementCount","acquire","priority","Promise","resolve","push","sort","a","b","release","resolveFunc","shift","setTimeout","Semaphore","semaphoreInstances","Error","hasSemaphoreInstance","Boolean","getSemaphoreInstance","tidy","_key","_priority","_ref2","hasTasks","request","fn","requestIfAvailable","GroupSemaphore","_semaphore","_activeCounts","_groupWaiters","_this$_activeCounts$k","_this$_groupWaiters$k","activeCount","waiter"],"mappings":"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"}
|
|
@@ -1,2 +1,2 @@
|
|
|
1
|
-
function e(e){var t=function(e){if("object"!=typeof e||!e)return e;var t=e[Symbol.toPrimitive];if(void 0!==t){var
|
|
1
|
+
function e(e){var t=function(e){if("object"!=typeof e||!e)return e;var t=e[Symbol.toPrimitive];if(void 0!==t){var r=t.call(e,"string");if("object"!=typeof r)return r;throw new TypeError("@@toPrimitive must return a primitive value.")}return String(e)}(e);return"symbol"==typeof t?t:t+""}var t="_default",r=function(e){return["string","number"].includes(typeof e)},n=function(e){var n;return null!=(n=r(e)?e:e.key)?n:t},i=/*#__PURE__*/function(){function t(e){this.queue=void 0,this.maxConcurrent=void 0,this.count=void 0,this.queue=[],this.maxConcurrent=e,this.count=0}var r,n,i=t.prototype;return i.incrementCount=function(){this.count++},i.decrementCount=function(){this.count--},i.acquire=function(e){var t=this;return this.canAcquire?(this.incrementCount(),Promise.resolve()):new Promise(function(r){t.queue.push({resolve:r,priority:e}),t.queue.sort(function(e,t){return t.priority-e.priority})})},i.release=function(){var e=this.queue.shift();e?setTimeout(e.resolve,0):this.decrementCount()},r=t,(n=[{key:"canAcquire",get:function(){return this.count<this.maxConcurrent}}])&&function(t,r){for(var n=0;n<r.length;n++){var i=r[n];i.enumerable=i.enumerable||!1,i.configurable=!0,"value"in i&&(i.writable=!0),Object.defineProperty(t,e(i.key),i)}}(r.prototype,n),Object.defineProperty(r,"prototype",{writable:!1}),r}(),o=/*#__PURE__*/function(){function e(e){if(void 0===e&&(e=1),this.semaphoreInstances=void 0,this.maxConcurrent=void 0,this.semaphoreInstances={},this.maxConcurrent=e,e<1)throw new Error("The maxConcurrent must be 1 or greater.")}var o=e.prototype;return o.hasSemaphoreInstance=function(e){return void 0===e&&(e=t),Boolean(this.semaphoreInstances[e])},o.getSemaphoreInstance=function(e){return void 0===e&&(e=t),this.hasSemaphoreInstance(e)||(this.semaphoreInstances[e]=new i(this.maxConcurrent)),this.semaphoreInstances[e]},o.tidy=function(e){void 0===e&&(e=t),this.hasSemaphoreInstance(e)&&0===this.getSemaphoreInstance(e).count&&delete this.semaphoreInstances[e]},o.canAcquire=function(e){void 0===e&&(e=t);var r=n(e);return!this.hasSemaphoreInstance(r)||this.getSemaphoreInstance(r).canAcquire},o.acquire=function(e){void 0===e&&(e=t);var i,o,s=n(e),u=null!=(o=r(i=e)?0:i.priority)?o:0;return this.getSemaphoreInstance(s).acquire(u)},o.release=function(e){void 0===e&&(e=t);var r=n(e);this.getSemaphoreInstance(r).release(),this.tidy(r)},o.count=function(e){void 0===e&&(e=t);var r=n(e);return this.hasSemaphoreInstance(r)?this.getSemaphoreInstance(r).count:0},o.hasTasks=function(e){return void 0===e&&(e=t),this.count(e)>0},o.request=function(e,r){void 0===r&&(r=t);try{var n=this;return Promise.resolve(function(t,i){try{var o=Promise.resolve(n.acquire(r)).then(function(){return Promise.resolve(e())})}catch(e){return i(!0,e)}return o&&o.then?o.then(i.bind(null,!1),i.bind(null,!0)):i(!1,o)}(0,function(e,t){if(n.release(r),e)throw t;return t}))}catch(e){return Promise.reject(e)}},o.requestIfAvailable=function(e,r){void 0===r&&(r=t);try{return this.canAcquire(r)?Promise.resolve(this.request(e,r)):Promise.resolve(null)}catch(e){return Promise.reject(e)}},e}(),s=/*#__PURE__*/function(){function e(){this._semaphore=new o,this._activeCounts={},this._groupWaiters={}}var t=e.prototype;return t.acquire=function(e){try{var t,r,n=this,i=null!=(t=n._activeCounts[e])?t:0;n._activeCounts[e]=i+1;var o=null!=(r=n._groupWaiters[e])?r:n._semaphore.acquire();return n._groupWaiters[e]=o,Promise.resolve(o).then(function(){})}catch(e){return Promise.reject(e)}},t.release=function(e){var t=this._activeCounts[e];1===t?(this._semaphore.release(),delete this._activeCounts[e],delete this._groupWaiters[e]):this._activeCounts[e]=t-1},e}();export{s as GroupSemaphore,o as Semaphore};
|
|
2
2
|
//# sourceMappingURL=promise-semaphore.module.js.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"promise-semaphore.module.js","sources":["../src/semaphore.ts","../src/group-semaphore.ts"],"sourcesContent":["class SemaphoreItem {\n private queue: Function[];\n private maxConcurrent: number;\n\n /**\n * The number of locks.\n */\n public count: number;\n\n constructor(maxConcurrent: number) {\n this.queue = [];\n this.maxConcurrent = maxConcurrent;\n this.count = 0;\n }\n\n get canAcquire(): boolean {\n return this.count < this.maxConcurrent;\n }\n\n private incrementCount() {\n this.count++;\n }\n\n private decrementCount() {\n this.count--;\n }\n\n acquire(): Promise<void> {\n if (this.canAcquire) {\n this.incrementCount();\n return Promise.resolve();\n } else {\n return new Promise((resolve) => this.queue.push(resolve));\n }\n }\n\n release(): void {\n const resolveFunc = this.queue.shift();\n\n if (resolveFunc) {\n // Give the micro task queue a small break instead of calling resolveFunc() directly\n setTimeout(resolveFunc, 0);\n } else {\n this.decrementCount();\n }\n }\n}\n\nconst defaultKey = \"_default\";\n\nclass Semaphore {\n private semaphoreInstances: Record<string | number, SemaphoreItem>;\n private maxConcurrent: number;\n\n /**\n * @param {number} [maxConcurrent] The maximum number of concurrent locks.\n */\n constructor(maxConcurrent: number = 1) {\n this.semaphoreInstances = {};\n this.maxConcurrent = maxConcurrent;\n }\n\n private hasSemaphoreInstance(key: string | number = defaultKey) {\n return Boolean(this.semaphoreInstances[key]);\n }\n\n private getSemaphoreInstance(key: string | number = defaultKey) {\n if (!this.hasSemaphoreInstance(key)) {\n this.semaphoreInstances[key] = new SemaphoreItem(\n this.maxConcurrent,\n );\n }\n return this.semaphoreInstances[key];\n }\n\n /**\n * @param {string | number} [key]- Optional, the semaphore key.\n */\n private tidy(key: string | number = defaultKey): void {\n if (\n this.hasSemaphoreInstance(key) &&\n this.getSemaphoreInstance(key).count === 0\n ) {\n delete this.semaphoreInstances[key];\n }\n }\n\n /**\n * A synchronous function to determine whether a lock can be acquired.\n *\n * @param {string | number} [key]- Optional, the semaphore key.\n * @returns {boolean} Returns true if the lock on `key` can be acquired, false\n * otherwise.\n */\n canAcquire(key: string | number = defaultKey): boolean {\n return !this.hasSemaphoreInstance(key) ||\n this.getSemaphoreInstance(key).canAcquire;\n }\n\n /**\n * @param {string | number} [key]- Optional, the semaphore key.\n */\n acquire(key: string | number = defaultKey) {\n return this.getSemaphoreInstance(key).acquire();\n }\n\n /**\n * @param {string | number} [key]- Optional, the semaphore key.\n */\n release(key: string | number = defaultKey): void {\n this.getSemaphoreInstance(key).release();\n this.tidy(key);\n }\n\n /**\n * The number of active locks. Will always be less or equal to `max`.\n *\n * @param {string | number} [key]- Optional, the semaphore key.\n */\n count(key: string | number = defaultKey): number {\n if (this.hasSemaphoreInstance(key)) {\n return this.getSemaphoreInstance(key).count;\n } else {\n return 0;\n }\n }\n\n /**\n * @param {string | number} [key]- Optional, the semaphore key.\n * @returns {boolean} True if the semaphore and key has locks, false otherwise.\n */\n hasTasks(key: string | number = defaultKey): boolean {\n return this.count(key) > 0;\n }\n\n /**\n * @param {Function<T>} fn The function to execute.\n * @param {string | number} [key]- Optional, the semaphore key.\n * @returns {Promise<T>}\n */\n async request<T>(\n fn: Function,\n key: string | number = defaultKey,\n ): Promise<T> {\n try {\n await this.acquire(key);\n return await fn();\n } finally {\n this.release(key);\n }\n }\n\n /**\n * Asynchronously executes `fn` if a lock can be immediately acquired.\n * Otherwise, returns null.\n *\n * @param {Function<T>} fn The function to execute.\n * @param {string | number} [key]- Optional, the semaphore key.\n * @returns {Promise<T>}\n */\n async requestIfAvailable<T>(\n fn: Function,\n key: string | number = defaultKey,\n ): Promise<T | null> {\n if (this.canAcquire(key)) {\n return this.request(fn, key);\n } else {\n return null;\n }\n }\n}\n\nexport { Semaphore };\n","import { Semaphore } from \"./semaphore\";\n\n/**\n * GroupSemaphore manages a shared semaphore for different groups of tasks. Each\n * group is identified by a unique key, and the semaphore ensures only one group\n * can run its tasks concurrently.\n *\n * - acquire(key): Increments the active count for the given group. If it's the\n * first task for the group (active count is 0), it acquires the global\n * semaphore, ensuring only one group's tasks can proceed at a time.\n * Subsequent calls in the group increment the count and are permitted to run.\n * - release(key): Decrements the active count for the group. If the last task\n * for that group is released, it releases the global semaphore, allowing\n * other groups to proceed.\n *\n * This ensures that only one group can execute concurrently, but multiple tasks\n * within the same group can run as long as no other tasks from different groups\n * are active.\n */\nclass GroupSemaphore {\n private _semaphore = new Semaphore();\n private _activeCounts: Record<string, number> = {};\n private _groupWaiters: Record<string, Promise<void>> = {};\n\n async acquire(key: string) {\n const activeCount = this._activeCounts[key] ?? 0;\n this._activeCounts[key] = activeCount + 1;\n const waiter = this._groupWaiters[key] ?? this._semaphore.acquire();\n this._groupWaiters[key] = waiter;\n await waiter;\n }\n\n release(key: string) {\n const activeCount = this._activeCounts[key];\n\n if (activeCount === 1) {\n this._semaphore.release();\n delete this._activeCounts[key];\n delete this._groupWaiters[key];\n } else {\n this._activeCounts[key] = activeCount - 1;\n }\n }\n}\n\nexport { GroupSemaphore };\n"],"names":["SemaphoreItem","maxConcurrent","queue","this","count","_proto","prototype","incrementCount","decrementCount","acquire","_this","canAcquire","Promise","resolve","push","release","resolveFunc","shift","setTimeout","key","get","defaultKey","Semaphore","semaphoreInstances","_proto2","hasSemaphoreInstance","Boolean","getSemaphoreInstance","tidy","hasTasks","request","fn","_this2","then","_finallyRethrows","_wasThrown","_result","e","reject","requestIfAvailable","GroupSemaphore","_semaphore","_activeCounts","_groupWaiters","_this$_activeCounts$k","_this$_groupWaiters$k","activeCount","waiter"],"mappings":"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"}
|
|
1
|
+
{"version":3,"file":"promise-semaphore.module.js","sources":["../src/semaphore.ts","../src/group-semaphore.ts"],"sourcesContent":["const defaultKey = \"_default\";\n\ntype KeyPrimitive = string | number;\n\ntype Key = KeyPrimitive | { key?: KeyPrimitive };\ntype KeyOptions = Key & { priority?: number };\n\nconst _isPrimitiveKey = (item: Key): item is KeyPrimitive =>\n [\"string\", \"number\"].includes(typeof item);\n\nconst resolveKey = (item: Key): KeyPrimitive =>\n (_isPrimitiveKey(item) ? item : item.key) ?? defaultKey;\n\nconst resolvePriority = (item: KeyOptions) =>\n (_isPrimitiveKey(item) ? 0 : item.priority) ?? 0;\n\nclass SemaphoreItem {\n private queue: Array<{\n resolve: Function;\n priority: number;\n }>;\n private maxConcurrent: number;\n\n /**\n * The number of locks.\n */\n public count: number;\n\n constructor(maxConcurrent: number) {\n this.queue = [];\n this.maxConcurrent = maxConcurrent;\n this.count = 0;\n }\n\n get canAcquire(): boolean {\n return this.count < this.maxConcurrent;\n }\n\n private incrementCount() {\n this.count++;\n }\n\n private decrementCount() {\n this.count--;\n }\n\n acquire(priority: number): Promise<void> {\n if (this.canAcquire) {\n this.incrementCount();\n return Promise.resolve();\n } else {\n return new Promise((resolve) => {\n this.queue.push({ resolve, priority });\n this.queue.sort((a, b) => b.priority - a.priority);\n });\n }\n }\n\n release(): void {\n const resolveFunc = this.queue.shift();\n\n if (resolveFunc) {\n // Give the micro task queue a small break instead of calling resolveFunc() directly\n setTimeout(resolveFunc.resolve, 0);\n } else {\n this.decrementCount();\n }\n }\n}\n\nclass Semaphore {\n private semaphoreInstances: Record<string | number, SemaphoreItem>;\n private maxConcurrent: number;\n\n /**\n * @param {number} [maxConcurrent] The maximum number of concurrent locks.\n */\n constructor(maxConcurrent: number = 1) {\n this.semaphoreInstances = {};\n this.maxConcurrent = maxConcurrent;\n\n if (maxConcurrent < 1) {\n throw new Error(\"The maxConcurrent must be 1 or greater.\");\n }\n }\n\n private hasSemaphoreInstance(key: KeyPrimitive = defaultKey) {\n return Boolean(this.semaphoreInstances[key]);\n }\n\n private getSemaphoreInstance(key: KeyPrimitive = defaultKey) {\n if (!this.hasSemaphoreInstance(key)) {\n this.semaphoreInstances[key] = new SemaphoreItem(\n this.maxConcurrent,\n );\n }\n return this.semaphoreInstances[key];\n }\n\n /**\n * @param {string | number} [key]- Optional, the semaphore key.\n */\n private tidy(key: KeyPrimitive = defaultKey): void {\n if (\n this.hasSemaphoreInstance(key) &&\n this.getSemaphoreInstance(key).count === 0\n ) {\n delete this.semaphoreInstances[key];\n }\n }\n\n /**\n * A synchronous function to determine whether a lock can be acquired.\n *\n * @param {string | number} [key]- Optional, the semaphore key.\n * @returns {boolean} Returns true if the lock on `key` can be acquired, false\n * otherwise.\n */\n canAcquire(key: Key = defaultKey): boolean {\n const _key = resolveKey(key);\n\n return !this.hasSemaphoreInstance(_key) ||\n this.getSemaphoreInstance(_key).canAcquire;\n }\n\n /**\n * @param {string | number} [key]- Optional, the semaphore key.\n */\n acquire(key: KeyOptions = defaultKey) {\n const _key = resolveKey(key);\n const _priority = resolvePriority(key);\n\n return this.getSemaphoreInstance(_key).acquire(_priority);\n }\n\n /**\n * @param {string | number} [key]- Optional, the semaphore key.\n */\n release(key: Key = defaultKey): void {\n const _key = resolveKey(key);\n\n this.getSemaphoreInstance(_key).release();\n this.tidy(_key);\n }\n\n /**\n * The number of active locks. Will always be less or equal to `max`.\n *\n * @param {string | number} [key]- Optional, the semaphore key.\n */\n count(key: Key = defaultKey): number {\n const _key = resolveKey(key);\n\n return (this.hasSemaphoreInstance(_key))\n ? this.getSemaphoreInstance(_key).count\n : 0;\n }\n\n /**\n * @param {string | number} [key]- Optional, the semaphore key.\n * @returns {boolean} True if the semaphore and key has locks, false otherwise.\n */\n hasTasks(key: Key = defaultKey): boolean {\n return this.count(key) > 0;\n }\n\n /**\n * @param {Function<T>} fn The function to execute.\n * @param {string | number} [key]- Optional, the semaphore key.\n * @returns {Promise<T>}\n */\n async request<T>(\n fn: Function,\n key: KeyOptions = defaultKey,\n ): Promise<T> {\n try {\n await this.acquire(key);\n return await fn();\n } finally {\n this.release(key);\n }\n }\n\n /**\n * Asynchronously executes `fn` if a lock can be immediately acquired.\n * Otherwise, returns null.\n *\n * @param {Function<T>} fn The function to execute.\n * @param {string | number} [key]- Optional, the semaphore key.\n * @returns {Promise<T>}\n */\n async requestIfAvailable<T>(\n fn: Function,\n key: KeyOptions = defaultKey,\n ): Promise<T | null> {\n if (this.canAcquire(key)) {\n return this.request(fn, key);\n } else {\n return null;\n }\n }\n}\n\nexport { Semaphore };\n","import { Semaphore } from \"./semaphore\";\n\n/**\n * GroupSemaphore manages a shared semaphore for different groups of tasks. Each\n * group is identified by a unique key, and the semaphore ensures only one group\n * can run its tasks concurrently.\n *\n * - acquire(key): Increments the active count for the given group. If it's the\n * first task for the group (active count is 0), it acquires the global\n * semaphore, ensuring only one group's tasks can proceed at a time.\n * Subsequent calls in the group increment the count and are permitted to run.\n * - release(key): Decrements the active count for the group. If the last task\n * for that group is released, it releases the global semaphore, allowing\n * other groups to proceed.\n *\n * This ensures that only one group can execute concurrently, but multiple tasks\n * within the same group can run as long as no other tasks from different groups\n * are active.\n */\nclass GroupSemaphore {\n private _semaphore = new Semaphore();\n private _activeCounts: Record<string, number> = {};\n private _groupWaiters: Record<string, Promise<void>> = {};\n\n async acquire(key: string) {\n const activeCount = this._activeCounts[key] ?? 0;\n this._activeCounts[key] = activeCount + 1;\n const waiter = this._groupWaiters[key] ?? this._semaphore.acquire();\n this._groupWaiters[key] = waiter;\n await waiter;\n }\n\n release(key: string) {\n const activeCount = this._activeCounts[key];\n\n if (activeCount === 1) {\n this._semaphore.release();\n delete this._activeCounts[key];\n delete this._groupWaiters[key];\n } else {\n this._activeCounts[key] = activeCount - 1;\n }\n }\n}\n\nexport { GroupSemaphore };\n"],"names":["defaultKey","_isPrimitiveKey","item","includes","resolveKey","_ref","key","SemaphoreItem","maxConcurrent","queue","this","count","_proto","prototype","_createClass","incrementCount","decrementCount","acquire","priority","_this","canAcquire","Promise","resolve","push","sort","a","b","release","resolveFunc","shift","setTimeout","get","Semaphore","semaphoreInstances","Error","_proto2","hasSemaphoreInstance","Boolean","getSemaphoreInstance","tidy","_key","_ref2","_priority","hasTasks","request","fn","_this2","then","_finallyRethrows","_wasThrown","_result","e","reject","requestIfAvailable","GroupSemaphore","_semaphore","_activeCounts","_groupWaiters","_this$_activeCounts$k","_this$_groupWaiters$k","activeCount","waiter"],"mappings":"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"}
|
package/lib/semaphore.d.ts
CHANGED
|
@@ -1,3 +1,10 @@
|
|
|
1
|
+
type KeyPrimitive = string | number;
|
|
2
|
+
type Key = KeyPrimitive | {
|
|
3
|
+
key?: KeyPrimitive;
|
|
4
|
+
};
|
|
5
|
+
type KeyOptions = Key & {
|
|
6
|
+
priority?: number;
|
|
7
|
+
};
|
|
1
8
|
declare class Semaphore {
|
|
2
9
|
private semaphoreInstances;
|
|
3
10
|
private maxConcurrent;
|
|
@@ -18,32 +25,32 @@ declare class Semaphore {
|
|
|
18
25
|
* @returns {boolean} Returns true if the lock on `key` can be acquired, false
|
|
19
26
|
* otherwise.
|
|
20
27
|
*/
|
|
21
|
-
canAcquire(key?:
|
|
28
|
+
canAcquire(key?: Key): boolean;
|
|
22
29
|
/**
|
|
23
30
|
* @param {string | number} [key]- Optional, the semaphore key.
|
|
24
31
|
*/
|
|
25
|
-
acquire(key?:
|
|
32
|
+
acquire(key?: KeyOptions): Promise<void>;
|
|
26
33
|
/**
|
|
27
34
|
* @param {string | number} [key]- Optional, the semaphore key.
|
|
28
35
|
*/
|
|
29
|
-
release(key?:
|
|
36
|
+
release(key?: Key): void;
|
|
30
37
|
/**
|
|
31
38
|
* The number of active locks. Will always be less or equal to `max`.
|
|
32
39
|
*
|
|
33
40
|
* @param {string | number} [key]- Optional, the semaphore key.
|
|
34
41
|
*/
|
|
35
|
-
count(key?:
|
|
42
|
+
count(key?: Key): number;
|
|
36
43
|
/**
|
|
37
44
|
* @param {string | number} [key]- Optional, the semaphore key.
|
|
38
45
|
* @returns {boolean} True if the semaphore and key has locks, false otherwise.
|
|
39
46
|
*/
|
|
40
|
-
hasTasks(key?:
|
|
47
|
+
hasTasks(key?: Key): boolean;
|
|
41
48
|
/**
|
|
42
49
|
* @param {Function<T>} fn The function to execute.
|
|
43
50
|
* @param {string | number} [key]- Optional, the semaphore key.
|
|
44
51
|
* @returns {Promise<T>}
|
|
45
52
|
*/
|
|
46
|
-
request<T>(fn: Function, key?:
|
|
53
|
+
request<T>(fn: Function, key?: KeyOptions): Promise<T>;
|
|
47
54
|
/**
|
|
48
55
|
* Asynchronously executes `fn` if a lock can be immediately acquired.
|
|
49
56
|
* Otherwise, returns null.
|
|
@@ -52,6 +59,6 @@ declare class Semaphore {
|
|
|
52
59
|
* @param {string | number} [key]- Optional, the semaphore key.
|
|
53
60
|
* @returns {Promise<T>}
|
|
54
61
|
*/
|
|
55
|
-
requestIfAvailable<T>(fn: Function, key?:
|
|
62
|
+
requestIfAvailable<T>(fn: Function, key?: KeyOptions): Promise<T | null>;
|
|
56
63
|
}
|
|
57
64
|
export { Semaphore };
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@chriscdn/promise-semaphore",
|
|
3
|
-
"version": "3.
|
|
3
|
+
"version": "3.1.2",
|
|
4
4
|
"description": "Limit or throttle the simultaneous execution of asynchronous code in separate iterations of the event loop.",
|
|
5
5
|
"repository": "https://github.com/chriscdn/promise-semaphore",
|
|
6
6
|
"author": "Christopher Meyer <chris@schwiiz.org>",
|
|
@@ -23,8 +23,11 @@
|
|
|
23
23
|
},
|
|
24
24
|
"devDependencies": {
|
|
25
25
|
"microbundle": "^0.15.1",
|
|
26
|
-
"vitest": "^
|
|
26
|
+
"vitest": "^4.0.5"
|
|
27
27
|
},
|
|
28
|
+
"files": [
|
|
29
|
+
"lib"
|
|
30
|
+
],
|
|
28
31
|
"keywords": [
|
|
29
32
|
"promise",
|
|
30
33
|
"semaphore",
|
|
@@ -32,6 +35,5 @@
|
|
|
32
35
|
"mutex",
|
|
33
36
|
"async",
|
|
34
37
|
"throttle"
|
|
35
|
-
]
|
|
36
|
-
"dependencies": {}
|
|
38
|
+
]
|
|
37
39
|
}
|
|
@@ -1,147 +0,0 @@
|
|
|
1
|
-
import { describe, expect, it } from "vitest";
|
|
2
|
-
|
|
3
|
-
import { Semaphore } from "../src/index";
|
|
4
|
-
|
|
5
|
-
const pause = async (ms) => new Promise((resolve) => setTimeout(resolve, ms));
|
|
6
|
-
|
|
7
|
-
describe("All test", () => {
|
|
8
|
-
it("Acquire & Release Basic", async () => {
|
|
9
|
-
const semaphore = new Semaphore();
|
|
10
|
-
expect(semaphore.canAcquire()).toBe(true);
|
|
11
|
-
await semaphore.acquire();
|
|
12
|
-
expect(semaphore.canAcquire()).toBe(false);
|
|
13
|
-
expect(semaphore.hasTasks()).toBe(true);
|
|
14
|
-
expect(semaphore.count()).toBe(1);
|
|
15
|
-
semaphore.release();
|
|
16
|
-
expect(semaphore.canAcquire()).toBe(true);
|
|
17
|
-
expect(semaphore.count()).toBe(0);
|
|
18
|
-
});
|
|
19
|
-
|
|
20
|
-
it("Semaphore 1", async () => {
|
|
21
|
-
const semaphore = new Semaphore();
|
|
22
|
-
|
|
23
|
-
semaphore
|
|
24
|
-
.acquire()
|
|
25
|
-
.then(async () => await pause(1000))
|
|
26
|
-
.finally(() => semaphore.release());
|
|
27
|
-
|
|
28
|
-
expect(semaphore.canAcquire()).toBe(false);
|
|
29
|
-
|
|
30
|
-
await pause(600);
|
|
31
|
-
|
|
32
|
-
expect(semaphore.canAcquire()).toBe(false);
|
|
33
|
-
|
|
34
|
-
await pause(600);
|
|
35
|
-
|
|
36
|
-
expect(semaphore.canAcquire()).toBe(true);
|
|
37
|
-
});
|
|
38
|
-
|
|
39
|
-
it("Semaphore 2", async () => {
|
|
40
|
-
const semaphore = new Semaphore();
|
|
41
|
-
|
|
42
|
-
let tester = 0;
|
|
43
|
-
|
|
44
|
-
semaphore
|
|
45
|
-
.acquire()
|
|
46
|
-
.then(() => pause(1000))
|
|
47
|
-
.then(() => (tester = 10))
|
|
48
|
-
.finally(() => semaphore.release());
|
|
49
|
-
|
|
50
|
-
// tests acquire waits for previous to complete
|
|
51
|
-
await semaphore
|
|
52
|
-
.acquire()
|
|
53
|
-
.then(() => expect(tester).toBe(10))
|
|
54
|
-
.finally(() => semaphore.release());
|
|
55
|
-
});
|
|
56
|
-
|
|
57
|
-
it("Semaphore 3", async () => {
|
|
58
|
-
const semaphore = new Semaphore(2);
|
|
59
|
-
|
|
60
|
-
let tester = 0;
|
|
61
|
-
|
|
62
|
-
semaphore
|
|
63
|
-
.acquire()
|
|
64
|
-
.then(() => pause(1000))
|
|
65
|
-
.then(() => (tester = 20))
|
|
66
|
-
.finally(() => semaphore.release());
|
|
67
|
-
|
|
68
|
-
semaphore
|
|
69
|
-
.acquire()
|
|
70
|
-
.then(() => pause(500))
|
|
71
|
-
.then(() => (tester = 10))
|
|
72
|
-
.finally(() => semaphore.release());
|
|
73
|
-
|
|
74
|
-
expect(semaphore.count()).toBe(2);
|
|
75
|
-
expect(semaphore.hasTasks()).toBe(true);
|
|
76
|
-
|
|
77
|
-
// expect 10 since this next block will run before the first has completed
|
|
78
|
-
await semaphore
|
|
79
|
-
.acquire()
|
|
80
|
-
.then(() => expect(tester).toBe(10))
|
|
81
|
-
.finally(() => semaphore.release());
|
|
82
|
-
|
|
83
|
-
expect(semaphore.count()).toBe(1);
|
|
84
|
-
expect(semaphore.hasTasks()).toBe(true);
|
|
85
|
-
expect(semaphore.canAcquire()).toBe(true);
|
|
86
|
-
});
|
|
87
|
-
|
|
88
|
-
it("Request 1", async () => {
|
|
89
|
-
const semaphore = new Semaphore();
|
|
90
|
-
|
|
91
|
-
let tester = 0;
|
|
92
|
-
|
|
93
|
-
semaphore.request(async () => {
|
|
94
|
-
await pause(1000);
|
|
95
|
-
tester = 20;
|
|
96
|
-
});
|
|
97
|
-
|
|
98
|
-
semaphore.request(async () => {
|
|
99
|
-
await pause(500);
|
|
100
|
-
tester = 10;
|
|
101
|
-
});
|
|
102
|
-
|
|
103
|
-
await semaphore.request(async () => {
|
|
104
|
-
expect(tester).toBe(10);
|
|
105
|
-
});
|
|
106
|
-
});
|
|
107
|
-
|
|
108
|
-
it("Request 2", async () => {
|
|
109
|
-
const semaphore = new Semaphore(2);
|
|
110
|
-
|
|
111
|
-
let tester = 0;
|
|
112
|
-
|
|
113
|
-
semaphore.request(async () => {
|
|
114
|
-
await pause(1000);
|
|
115
|
-
tester = 20;
|
|
116
|
-
});
|
|
117
|
-
|
|
118
|
-
semaphore.request(async () => {
|
|
119
|
-
await pause(500);
|
|
120
|
-
tester = 10;
|
|
121
|
-
});
|
|
122
|
-
|
|
123
|
-
await semaphore.request(async () => {
|
|
124
|
-
expect(tester).toBe(10);
|
|
125
|
-
});
|
|
126
|
-
});
|
|
127
|
-
|
|
128
|
-
it("Request 3", async () => {
|
|
129
|
-
const semaphore = new Semaphore(3);
|
|
130
|
-
|
|
131
|
-
let tester = 0;
|
|
132
|
-
|
|
133
|
-
semaphore.request(async () => {
|
|
134
|
-
await pause(1000);
|
|
135
|
-
tester = 20;
|
|
136
|
-
});
|
|
137
|
-
|
|
138
|
-
semaphore.request(async () => {
|
|
139
|
-
await pause(500);
|
|
140
|
-
tester = 10;
|
|
141
|
-
});
|
|
142
|
-
|
|
143
|
-
await semaphore.request(async () => {
|
|
144
|
-
expect(tester).toBe(0);
|
|
145
|
-
});
|
|
146
|
-
});
|
|
147
|
-
});
|
package/src/group-semaphore.ts
DELETED
|
@@ -1,46 +0,0 @@
|
|
|
1
|
-
import { Semaphore } from "./semaphore";
|
|
2
|
-
|
|
3
|
-
/**
|
|
4
|
-
* GroupSemaphore manages a shared semaphore for different groups of tasks. Each
|
|
5
|
-
* group is identified by a unique key, and the semaphore ensures only one group
|
|
6
|
-
* can run its tasks concurrently.
|
|
7
|
-
*
|
|
8
|
-
* - acquire(key): Increments the active count for the given group. If it's the
|
|
9
|
-
* first task for the group (active count is 0), it acquires the global
|
|
10
|
-
* semaphore, ensuring only one group's tasks can proceed at a time.
|
|
11
|
-
* Subsequent calls in the group increment the count and are permitted to run.
|
|
12
|
-
* - release(key): Decrements the active count for the group. If the last task
|
|
13
|
-
* for that group is released, it releases the global semaphore, allowing
|
|
14
|
-
* other groups to proceed.
|
|
15
|
-
*
|
|
16
|
-
* This ensures that only one group can execute concurrently, but multiple tasks
|
|
17
|
-
* within the same group can run as long as no other tasks from different groups
|
|
18
|
-
* are active.
|
|
19
|
-
*/
|
|
20
|
-
class GroupSemaphore {
|
|
21
|
-
private _semaphore = new Semaphore();
|
|
22
|
-
private _activeCounts: Record<string, number> = {};
|
|
23
|
-
private _groupWaiters: Record<string, Promise<void>> = {};
|
|
24
|
-
|
|
25
|
-
async acquire(key: string) {
|
|
26
|
-
const activeCount = this._activeCounts[key] ?? 0;
|
|
27
|
-
this._activeCounts[key] = activeCount + 1;
|
|
28
|
-
const waiter = this._groupWaiters[key] ?? this._semaphore.acquire();
|
|
29
|
-
this._groupWaiters[key] = waiter;
|
|
30
|
-
await waiter;
|
|
31
|
-
}
|
|
32
|
-
|
|
33
|
-
release(key: string) {
|
|
34
|
-
const activeCount = this._activeCounts[key];
|
|
35
|
-
|
|
36
|
-
if (activeCount === 1) {
|
|
37
|
-
this._semaphore.release();
|
|
38
|
-
delete this._activeCounts[key];
|
|
39
|
-
delete this._groupWaiters[key];
|
|
40
|
-
} else {
|
|
41
|
-
this._activeCounts[key] = activeCount - 1;
|
|
42
|
-
}
|
|
43
|
-
}
|
|
44
|
-
}
|
|
45
|
-
|
|
46
|
-
export { GroupSemaphore };
|
package/src/index.ts
DELETED
package/src/semaphore.ts
DELETED
|
@@ -1,173 +0,0 @@
|
|
|
1
|
-
class SemaphoreItem {
|
|
2
|
-
private queue: Function[];
|
|
3
|
-
private maxConcurrent: number;
|
|
4
|
-
|
|
5
|
-
/**
|
|
6
|
-
* The number of locks.
|
|
7
|
-
*/
|
|
8
|
-
public count: number;
|
|
9
|
-
|
|
10
|
-
constructor(maxConcurrent: number) {
|
|
11
|
-
this.queue = [];
|
|
12
|
-
this.maxConcurrent = maxConcurrent;
|
|
13
|
-
this.count = 0;
|
|
14
|
-
}
|
|
15
|
-
|
|
16
|
-
get canAcquire(): boolean {
|
|
17
|
-
return this.count < this.maxConcurrent;
|
|
18
|
-
}
|
|
19
|
-
|
|
20
|
-
private incrementCount() {
|
|
21
|
-
this.count++;
|
|
22
|
-
}
|
|
23
|
-
|
|
24
|
-
private decrementCount() {
|
|
25
|
-
this.count--;
|
|
26
|
-
}
|
|
27
|
-
|
|
28
|
-
acquire(): Promise<void> {
|
|
29
|
-
if (this.canAcquire) {
|
|
30
|
-
this.incrementCount();
|
|
31
|
-
return Promise.resolve();
|
|
32
|
-
} else {
|
|
33
|
-
return new Promise((resolve) => this.queue.push(resolve));
|
|
34
|
-
}
|
|
35
|
-
}
|
|
36
|
-
|
|
37
|
-
release(): void {
|
|
38
|
-
const resolveFunc = this.queue.shift();
|
|
39
|
-
|
|
40
|
-
if (resolveFunc) {
|
|
41
|
-
// Give the micro task queue a small break instead of calling resolveFunc() directly
|
|
42
|
-
setTimeout(resolveFunc, 0);
|
|
43
|
-
} else {
|
|
44
|
-
this.decrementCount();
|
|
45
|
-
}
|
|
46
|
-
}
|
|
47
|
-
}
|
|
48
|
-
|
|
49
|
-
const defaultKey = "_default";
|
|
50
|
-
|
|
51
|
-
class Semaphore {
|
|
52
|
-
private semaphoreInstances: Record<string | number, SemaphoreItem>;
|
|
53
|
-
private maxConcurrent: number;
|
|
54
|
-
|
|
55
|
-
/**
|
|
56
|
-
* @param {number} [maxConcurrent] The maximum number of concurrent locks.
|
|
57
|
-
*/
|
|
58
|
-
constructor(maxConcurrent: number = 1) {
|
|
59
|
-
this.semaphoreInstances = {};
|
|
60
|
-
this.maxConcurrent = maxConcurrent;
|
|
61
|
-
}
|
|
62
|
-
|
|
63
|
-
private hasSemaphoreInstance(key: string | number = defaultKey) {
|
|
64
|
-
return Boolean(this.semaphoreInstances[key]);
|
|
65
|
-
}
|
|
66
|
-
|
|
67
|
-
private getSemaphoreInstance(key: string | number = defaultKey) {
|
|
68
|
-
if (!this.hasSemaphoreInstance(key)) {
|
|
69
|
-
this.semaphoreInstances[key] = new SemaphoreItem(
|
|
70
|
-
this.maxConcurrent,
|
|
71
|
-
);
|
|
72
|
-
}
|
|
73
|
-
return this.semaphoreInstances[key];
|
|
74
|
-
}
|
|
75
|
-
|
|
76
|
-
/**
|
|
77
|
-
* @param {string | number} [key]- Optional, the semaphore key.
|
|
78
|
-
*/
|
|
79
|
-
private tidy(key: string | number = defaultKey): void {
|
|
80
|
-
if (
|
|
81
|
-
this.hasSemaphoreInstance(key) &&
|
|
82
|
-
this.getSemaphoreInstance(key).count === 0
|
|
83
|
-
) {
|
|
84
|
-
delete this.semaphoreInstances[key];
|
|
85
|
-
}
|
|
86
|
-
}
|
|
87
|
-
|
|
88
|
-
/**
|
|
89
|
-
* A synchronous function to determine whether a lock can be acquired.
|
|
90
|
-
*
|
|
91
|
-
* @param {string | number} [key]- Optional, the semaphore key.
|
|
92
|
-
* @returns {boolean} Returns true if the lock on `key` can be acquired, false
|
|
93
|
-
* otherwise.
|
|
94
|
-
*/
|
|
95
|
-
canAcquire(key: string | number = defaultKey): boolean {
|
|
96
|
-
return !this.hasSemaphoreInstance(key) ||
|
|
97
|
-
this.getSemaphoreInstance(key).canAcquire;
|
|
98
|
-
}
|
|
99
|
-
|
|
100
|
-
/**
|
|
101
|
-
* @param {string | number} [key]- Optional, the semaphore key.
|
|
102
|
-
*/
|
|
103
|
-
acquire(key: string | number = defaultKey) {
|
|
104
|
-
return this.getSemaphoreInstance(key).acquire();
|
|
105
|
-
}
|
|
106
|
-
|
|
107
|
-
/**
|
|
108
|
-
* @param {string | number} [key]- Optional, the semaphore key.
|
|
109
|
-
*/
|
|
110
|
-
release(key: string | number = defaultKey): void {
|
|
111
|
-
this.getSemaphoreInstance(key).release();
|
|
112
|
-
this.tidy(key);
|
|
113
|
-
}
|
|
114
|
-
|
|
115
|
-
/**
|
|
116
|
-
* The number of active locks. Will always be less or equal to `max`.
|
|
117
|
-
*
|
|
118
|
-
* @param {string | number} [key]- Optional, the semaphore key.
|
|
119
|
-
*/
|
|
120
|
-
count(key: string | number = defaultKey): number {
|
|
121
|
-
if (this.hasSemaphoreInstance(key)) {
|
|
122
|
-
return this.getSemaphoreInstance(key).count;
|
|
123
|
-
} else {
|
|
124
|
-
return 0;
|
|
125
|
-
}
|
|
126
|
-
}
|
|
127
|
-
|
|
128
|
-
/**
|
|
129
|
-
* @param {string | number} [key]- Optional, the semaphore key.
|
|
130
|
-
* @returns {boolean} True if the semaphore and key has locks, false otherwise.
|
|
131
|
-
*/
|
|
132
|
-
hasTasks(key: string | number = defaultKey): boolean {
|
|
133
|
-
return this.count(key) > 0;
|
|
134
|
-
}
|
|
135
|
-
|
|
136
|
-
/**
|
|
137
|
-
* @param {Function<T>} fn The function to execute.
|
|
138
|
-
* @param {string | number} [key]- Optional, the semaphore key.
|
|
139
|
-
* @returns {Promise<T>}
|
|
140
|
-
*/
|
|
141
|
-
async request<T>(
|
|
142
|
-
fn: Function,
|
|
143
|
-
key: string | number = defaultKey,
|
|
144
|
-
): Promise<T> {
|
|
145
|
-
try {
|
|
146
|
-
await this.acquire(key);
|
|
147
|
-
return await fn();
|
|
148
|
-
} finally {
|
|
149
|
-
this.release(key);
|
|
150
|
-
}
|
|
151
|
-
}
|
|
152
|
-
|
|
153
|
-
/**
|
|
154
|
-
* Asynchronously executes `fn` if a lock can be immediately acquired.
|
|
155
|
-
* Otherwise, returns null.
|
|
156
|
-
*
|
|
157
|
-
* @param {Function<T>} fn The function to execute.
|
|
158
|
-
* @param {string | number} [key]- Optional, the semaphore key.
|
|
159
|
-
* @returns {Promise<T>}
|
|
160
|
-
*/
|
|
161
|
-
async requestIfAvailable<T>(
|
|
162
|
-
fn: Function,
|
|
163
|
-
key: string | number = defaultKey,
|
|
164
|
-
): Promise<T | null> {
|
|
165
|
-
if (this.canAcquire(key)) {
|
|
166
|
-
return this.request(fn, key);
|
|
167
|
-
} else {
|
|
168
|
-
return null;
|
|
169
|
-
}
|
|
170
|
-
}
|
|
171
|
-
}
|
|
172
|
-
|
|
173
|
-
export { Semaphore };
|