@chriscdn/promise-semaphore 3.0.0 → 3.1.1
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 +85 -38
- package/__tests__/semaphore.test.ts +27 -0
- package/lib/group-semaphore.d.ts +25 -0
- package/lib/index.d.ts +2 -57
- 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 +64 -0
- package/package.json +2 -2
- package/src/group-semaphore.ts +46 -0
- package/src/index.ts +2 -171
- package/src/semaphore.ts +200 -0
package/README.md
CHANGED
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
# @chriscdn/promise-semaphore
|
|
2
2
|
|
|
3
|
-
Limit or throttle the
|
|
3
|
+
Limit or throttle the concurrent execution of asynchronous code in separate
|
|
4
|
+
iterations of the event loop.
|
|
4
5
|
|
|
5
6
|
## Installing
|
|
6
7
|
|
|
@@ -16,25 +17,30 @@ Using yarn:
|
|
|
16
17
|
yarn add @chriscdn/promise-semaphore
|
|
17
18
|
```
|
|
18
19
|
|
|
19
|
-
##
|
|
20
|
+
## Version 3
|
|
20
21
|
|
|
21
|
-
|
|
22
|
+
Version 3 introduces two main changes:
|
|
23
|
+
|
|
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.
|
|
25
|
+
- The default export has been replaced with a named export.
|
|
26
|
+
|
|
27
|
+
Change:
|
|
22
28
|
|
|
23
29
|
```ts
|
|
24
30
|
import Semaphore from "@chriscdn/promise-semaphore";
|
|
25
31
|
```
|
|
26
32
|
|
|
27
|
-
to
|
|
33
|
+
to:
|
|
28
34
|
|
|
29
35
|
```ts
|
|
30
36
|
import { Semaphore } from "@chriscdn/promise-semaphore";
|
|
31
37
|
```
|
|
32
38
|
|
|
33
|
-
## API
|
|
39
|
+
## API - Semaphore
|
|
34
40
|
|
|
35
41
|
### Create an instance
|
|
36
42
|
|
|
37
|
-
```
|
|
43
|
+
```ts
|
|
38
44
|
import { Semaphore } from "@chriscdn/promise-semaphore";
|
|
39
45
|
const semaphore = new Semaphore([maxConcurrent]);
|
|
40
46
|
```
|
|
@@ -43,23 +49,30 @@ The `maxConcurrent` parameter is optional and defaults to `1` (making it an excl
|
|
|
43
49
|
|
|
44
50
|
### Acquire a lock
|
|
45
51
|
|
|
46
|
-
```
|
|
47
|
-
semaphore.acquire([
|
|
52
|
+
```ts
|
|
53
|
+
semaphore.acquire([options]);
|
|
48
54
|
```
|
|
49
55
|
|
|
50
|
-
This returns a `Promise` that resolves
|
|
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.
|
|
51
64
|
|
|
52
65
|
### Release a lock
|
|
53
66
|
|
|
54
|
-
```
|
|
67
|
+
```ts
|
|
55
68
|
semaphore.release([key]);
|
|
56
69
|
```
|
|
57
70
|
|
|
58
|
-
The `release` method should be called within a `finally` block (whether using 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.
|
|
59
72
|
|
|
60
73
|
### Check if a lock can be acquired
|
|
61
74
|
|
|
62
|
-
```
|
|
75
|
+
```ts
|
|
63
76
|
semaphore.canAcquire([key]);
|
|
64
77
|
```
|
|
65
78
|
|
|
@@ -67,24 +80,24 @@ This synchronous method returns `true` if a lock can be immediately acquired, an
|
|
|
67
80
|
|
|
68
81
|
### `count`
|
|
69
82
|
|
|
70
|
-
```
|
|
83
|
+
```ts
|
|
71
84
|
semaphore.count([key]);
|
|
72
85
|
```
|
|
73
86
|
|
|
74
|
-
This synchronous
|
|
87
|
+
This function is synchronous, and returns the current number of locks.
|
|
75
88
|
|
|
76
89
|
### `request` method
|
|
77
90
|
|
|
78
|
-
```
|
|
79
|
-
const results = await semaphore.request(fn [,
|
|
91
|
+
```ts
|
|
92
|
+
const results = await semaphore.request(fn [, options]);
|
|
80
93
|
```
|
|
81
94
|
|
|
82
95
|
This function reduces boilerplate when using `acquire` and `release`. It returns a promise that resolves when `fn` completes. It is functionally equivalent to:
|
|
83
96
|
|
|
84
|
-
```
|
|
97
|
+
```ts
|
|
85
98
|
try {
|
|
86
|
-
await semaphore.acquire([
|
|
87
|
-
|
|
99
|
+
await semaphore.acquire([options]);
|
|
100
|
+
return await fn();
|
|
88
101
|
} finally {
|
|
89
102
|
semaphore.release([key]);
|
|
90
103
|
}
|
|
@@ -92,15 +105,15 @@ try {
|
|
|
92
105
|
|
|
93
106
|
### `requestIfAvailable` method
|
|
94
107
|
|
|
95
|
-
```
|
|
96
|
-
const results = await semaphore.requestIfAvailable(fn [,
|
|
108
|
+
```ts
|
|
109
|
+
const results = await semaphore.requestIfAvailable(fn [, options]);
|
|
97
110
|
```
|
|
98
111
|
|
|
99
112
|
This is functionally equivalent to:
|
|
100
113
|
|
|
101
|
-
```
|
|
102
|
-
|
|
103
|
-
? await semaphore.request(fn, [
|
|
114
|
+
```ts
|
|
115
|
+
return semaphore.canAcquire([key])
|
|
116
|
+
? await semaphore.request(fn, [options])
|
|
104
117
|
: null;
|
|
105
118
|
```
|
|
106
119
|
|
|
@@ -108,7 +121,7 @@ This is useful in scenarios where only one instance of a function block should r
|
|
|
108
121
|
|
|
109
122
|
## Example 1
|
|
110
123
|
|
|
111
|
-
```
|
|
124
|
+
```ts
|
|
112
125
|
import { Semaphore } from "@chriscdn/promise-semaphore";
|
|
113
126
|
const semaphore = new Semaphore();
|
|
114
127
|
|
|
@@ -119,7 +132,7 @@ semaphore
|
|
|
119
132
|
// This block executes once a lock is acquired.
|
|
120
133
|
// If already locked, it waits and executes after all preceding locks are released.
|
|
121
134
|
//
|
|
122
|
-
// Critical operations
|
|
135
|
+
// Critical operations
|
|
123
136
|
})
|
|
124
137
|
.finally(() => {
|
|
125
138
|
// The lock is released, allowing the next queued block to proceed.
|
|
@@ -130,14 +143,14 @@ semaphore
|
|
|
130
143
|
try {
|
|
131
144
|
await semaphore.acquire();
|
|
132
145
|
|
|
133
|
-
// Critical operations
|
|
146
|
+
// Critical operations
|
|
134
147
|
} finally {
|
|
135
148
|
semaphore.release();
|
|
136
149
|
}
|
|
137
150
|
|
|
138
151
|
// Using the request function
|
|
139
152
|
await semaphore.request(() => {
|
|
140
|
-
// Critical operations
|
|
153
|
+
// Critical operations
|
|
141
154
|
});
|
|
142
155
|
```
|
|
143
156
|
|
|
@@ -145,7 +158,7 @@ await semaphore.request(() => {
|
|
|
145
158
|
|
|
146
159
|
Consider an asynchronous function that downloads a file and saves it to disk:
|
|
147
160
|
|
|
148
|
-
```
|
|
161
|
+
```ts
|
|
149
162
|
const downloadAndSave = async (url) => {
|
|
150
163
|
const filePath = urlToFilePath(url);
|
|
151
164
|
|
|
@@ -159,11 +172,11 @@ const downloadAndSave = async (url) => {
|
|
|
159
172
|
};
|
|
160
173
|
```
|
|
161
174
|
|
|
162
|
-
This approach works as expected until `downloadAndSave()` is called multiple times with the same `url
|
|
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.
|
|
163
176
|
|
|
164
177
|
This issue can be resolved by using a `Semaphore` with the `key` parameter:
|
|
165
178
|
|
|
166
|
-
```
|
|
179
|
+
```ts
|
|
167
180
|
import { Semaphore } from "@chriscdn/promise-semaphore";
|
|
168
181
|
const semaphore = new Semaphore();
|
|
169
182
|
|
|
@@ -172,7 +185,7 @@ const downloadAndSave = async (url) => {
|
|
|
172
185
|
await semaphore.acquire(url);
|
|
173
186
|
|
|
174
187
|
// This block continues once a lock on url is acquired. This
|
|
175
|
-
// permits multiple
|
|
188
|
+
// permits multiple simultaneous downloads for different urls.
|
|
176
189
|
|
|
177
190
|
const filePath = urlToFilePath(url);
|
|
178
191
|
|
|
@@ -189,25 +202,59 @@ const downloadAndSave = async (url) => {
|
|
|
189
202
|
};
|
|
190
203
|
```
|
|
191
204
|
|
|
192
|
-
The same outcome can be achieved using the `request` function:
|
|
205
|
+
The same outcome can be achieved by using the `request` function:
|
|
193
206
|
|
|
194
|
-
```
|
|
207
|
+
```ts
|
|
195
208
|
const downloadAndSave = (url) => {
|
|
196
209
|
return semaphore.request(async () => {
|
|
197
210
|
const filePath = urlToFilePath(url);
|
|
198
211
|
|
|
199
212
|
if (await pathExists(filePath)) {
|
|
200
213
|
// The file is already on disk, so no action is required.
|
|
201
|
-
|
|
214
|
+
} else {
|
|
215
|
+
await downloadAndSaveToFilepath(url, filePath);
|
|
202
216
|
}
|
|
203
|
-
|
|
204
|
-
await downloadAndSaveToFilepath(url, filePath);
|
|
205
|
-
|
|
206
217
|
return filePath;
|
|
207
218
|
}, url);
|
|
208
219
|
};
|
|
209
220
|
```
|
|
210
221
|
|
|
222
|
+
## API - GroupSemaphore
|
|
223
|
+
|
|
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.
|
|
225
|
+
|
|
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.
|
|
227
|
+
|
|
228
|
+
### Example
|
|
229
|
+
|
|
230
|
+
```ts
|
|
231
|
+
import { GroupSemaphore } from "@chriscdn/promise-semaphore";
|
|
232
|
+
|
|
233
|
+
const groupSemaphore = new GroupSemaphore();
|
|
234
|
+
|
|
235
|
+
const RunA = async () => {
|
|
236
|
+
try {
|
|
237
|
+
await groupSemaphore.acquire("GroupA");
|
|
238
|
+
|
|
239
|
+
// Perform asynchronous operations for group A
|
|
240
|
+
} finally {
|
|
241
|
+
groupSemaphore.release("GroupA");
|
|
242
|
+
}
|
|
243
|
+
};
|
|
244
|
+
|
|
245
|
+
const RunB = async () => {
|
|
246
|
+
try {
|
|
247
|
+
await groupSemaphore.acquire("GroupB");
|
|
248
|
+
|
|
249
|
+
// Perform asynchronous operations for group B
|
|
250
|
+
} finally {
|
|
251
|
+
groupSemaphore.release("GroupB");
|
|
252
|
+
}
|
|
253
|
+
};
|
|
254
|
+
```
|
|
255
|
+
|
|
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.
|
|
257
|
+
|
|
211
258
|
## License
|
|
212
259
|
|
|
213
260
|
[MIT](LICENSE)
|
|
@@ -145,3 +145,30 @@ describe("All test", () => {
|
|
|
145
145
|
});
|
|
146
146
|
});
|
|
147
147
|
});
|
|
148
|
+
|
|
149
|
+
describe("Priority", () => {
|
|
150
|
+
it("Priority", async () => {
|
|
151
|
+
const semaphore = new Semaphore(1);
|
|
152
|
+
let tester = 0;
|
|
153
|
+
let key = "yyz";
|
|
154
|
+
|
|
155
|
+
// this one is executed immediately since it's the first object
|
|
156
|
+
const p1 = semaphore.request(async () => {
|
|
157
|
+
tester = 5;
|
|
158
|
+
}, { key, priority: 0 });
|
|
159
|
+
|
|
160
|
+
// this one gets queued with priority 20
|
|
161
|
+
const p2 = semaphore.request(async () => {
|
|
162
|
+
tester = 10;
|
|
163
|
+
}, { key, priority: 20 });
|
|
164
|
+
|
|
165
|
+
// this one gets queued with priority 30, which should execute before p2
|
|
166
|
+
const p3 = semaphore.request(async () => {
|
|
167
|
+
expect(tester).toBe(5);
|
|
168
|
+
}, { key, priority: 30 });
|
|
169
|
+
|
|
170
|
+
semaphore.acquire({ priority: 5 });
|
|
171
|
+
|
|
172
|
+
await Promise.all([p1, p2, p3]);
|
|
173
|
+
});
|
|
174
|
+
});
|
|
@@ -0,0 +1,25 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* GroupSemaphore manages a shared semaphore for different groups of tasks. Each
|
|
3
|
+
* group is identified by a unique key, and the semaphore ensures only one group
|
|
4
|
+
* can run its tasks concurrently.
|
|
5
|
+
*
|
|
6
|
+
* - acquire(key): Increments the active count for the given group. If it's the
|
|
7
|
+
* first task for the group (active count is 0), it acquires the global
|
|
8
|
+
* semaphore, ensuring only one group's tasks can proceed at a time.
|
|
9
|
+
* Subsequent calls in the group increment the count and are permitted to run.
|
|
10
|
+
* - release(key): Decrements the active count for the group. If the last task
|
|
11
|
+
* for that group is released, it releases the global semaphore, allowing
|
|
12
|
+
* other groups to proceed.
|
|
13
|
+
*
|
|
14
|
+
* This ensures that only one group can execute concurrently, but multiple tasks
|
|
15
|
+
* within the same group can run as long as no other tasks from different groups
|
|
16
|
+
* are active.
|
|
17
|
+
*/
|
|
18
|
+
declare class GroupSemaphore {
|
|
19
|
+
private _semaphore;
|
|
20
|
+
private _activeCounts;
|
|
21
|
+
private _groupWaiters;
|
|
22
|
+
acquire(key: string): Promise<void>;
|
|
23
|
+
release(key: string): void;
|
|
24
|
+
}
|
|
25
|
+
export { GroupSemaphore };
|
package/lib/index.d.ts
CHANGED
|
@@ -1,57 +1,2 @@
|
|
|
1
|
-
|
|
2
|
-
|
|
3
|
-
private maxConcurrent;
|
|
4
|
-
/**
|
|
5
|
-
* @param {number} [maxConcurrent] The maximum number of concurrent locks.
|
|
6
|
-
*/
|
|
7
|
-
constructor(maxConcurrent?: number);
|
|
8
|
-
private hasSemaphoreInstance;
|
|
9
|
-
private getSemaphoreInstance;
|
|
10
|
-
/**
|
|
11
|
-
* @param {string | number} [key]- Optional, the semaphore key.
|
|
12
|
-
*/
|
|
13
|
-
private tidy;
|
|
14
|
-
/**
|
|
15
|
-
* A synchronous function to determine whether a lock can be acquired.
|
|
16
|
-
*
|
|
17
|
-
* @param {string | number} [key]- Optional, the semaphore key.
|
|
18
|
-
* @returns {boolean} Returns true if the lock on `key` can be acquired, false
|
|
19
|
-
* otherwise.
|
|
20
|
-
*/
|
|
21
|
-
canAcquire(key?: string | number): boolean;
|
|
22
|
-
/**
|
|
23
|
-
* @param {string | number} [key]- Optional, the semaphore key.
|
|
24
|
-
*/
|
|
25
|
-
acquire(key?: string | number): Promise<void>;
|
|
26
|
-
/**
|
|
27
|
-
* @param {string | number} [key]- Optional, the semaphore key.
|
|
28
|
-
*/
|
|
29
|
-
release(key?: string | number): void;
|
|
30
|
-
/**
|
|
31
|
-
* The number of active locks. Will always be less or equal to `max`.
|
|
32
|
-
*
|
|
33
|
-
* @param {string | number} [key]- Optional, the semaphore key.
|
|
34
|
-
*/
|
|
35
|
-
count(key?: string | number): number;
|
|
36
|
-
/**
|
|
37
|
-
* @param {string | number} [key]- Optional, the semaphore key.
|
|
38
|
-
* @returns {boolean} True if the semaphore and key has locks, false otherwise.
|
|
39
|
-
*/
|
|
40
|
-
hasTasks(key?: string | number): boolean;
|
|
41
|
-
/**
|
|
42
|
-
* @param {Function<T>} fn The function to execute.
|
|
43
|
-
* @param {string | number} [key]- Optional, the semaphore key.
|
|
44
|
-
* @returns {Promise<T>}
|
|
45
|
-
*/
|
|
46
|
-
request<T>(fn: Function, key?: string | number): Promise<T>;
|
|
47
|
-
/**
|
|
48
|
-
* Asynchronously executes `fn` if a lock can be immediately acquired.
|
|
49
|
-
* Otherwise, returns null.
|
|
50
|
-
*
|
|
51
|
-
* @param {Function<T>} fn The function to execute.
|
|
52
|
-
* @param {string | number} [key]- Optional, the semaphore key.
|
|
53
|
-
* @returns {Promise<T>}
|
|
54
|
-
*/
|
|
55
|
-
requestIfAvailable<T>(fn: Function, key?: string | number): Promise<T | null>;
|
|
56
|
-
}
|
|
57
|
-
export { Semaphore };
|
|
1
|
+
export { Semaphore } from "./semaphore";
|
|
2
|
+
export { GroupSemaphore } from "./group-semaphore";
|
|
@@ -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){void 0===e&&(e=1),this.semaphoreInstances=void 0,this.maxConcurrent=void 0,this.semaphoreInstances={},this.maxConcurrent=e}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/index.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(this.maxConcurrent);\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"],"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"],"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\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","count","this","_proto","prototype","incrementCount","decrementCount","acquire","priority","_this","canAcquire","Promise","resolve","push","sort","a","b","release","resolveFunc","shift","setTimeout","get","Semaphore","semaphoreInstances","_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 n;return null!=(n=t(s)?s:s.key)?n:e};class n{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 r{constructor(e=1){this.semaphoreInstances=void 0,this.maxConcurrent=void 0,this.semaphoreInstances={},this.maxConcurrent=e}hasSemaphoreInstance(t=e){return Boolean(this.semaphoreInstances[t])}getSemaphoreInstance(t=e){return this.hasSemaphoreInstance(t)||(this.semaphoreInstances[t]=new n(this.maxConcurrent)),this.semaphoreInstances[t]}tidy(t=e){this.hasSemaphoreInstance(t)&&0===this.getSemaphoreInstance(t).count&&delete this.semaphoreInstances[t]}canAcquire(t=e){const n=s(t);return!this.hasSemaphoreInstance(n)||this.getSemaphoreInstance(n).canAcquire}acquire(n=e){const r=s(n),i=null!=(o=t(a=n)?0:a.priority)?o:0;var a,o;return this.getSemaphoreInstance(r).acquire(i)}release(t=e){const n=s(t);this.getSemaphoreInstance(n).release(),this.tidy(n)}count(t=e){const n=s(t);return this.hasSemaphoreInstance(n)?this.getSemaphoreInstance(n).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 r,this._activeCounts={},this._groupWaiters={}}async acquire(e){var t,s;const n=null!=(t=this._activeCounts[e])?t:0;this._activeCounts[e]=n+1;const r=null!=(s=this._groupWaiters[e])?s:this._semaphore.acquire();this._groupWaiters[e]=r,await r}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,r as Semaphore};
|
|
2
2
|
//# sourceMappingURL=promise-semaphore.modern.js.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"promise-semaphore.modern.js","sources":["../src/index.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(this.maxConcurrent);\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"],"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"],"mappings":"AAAA,MAAMA,EASJC,WAAAA,CAAYC,GAAqBC,KARzBC,WAAK,EAAAD,KACLD,mBAAa,EAAAC,KAKdE,WAGL,EAAAF,KAAKC,MAAQ,GACbD,KAAKD,cAAgBA,EACrBC,KAAKE,MAAQ,CACf,CAEA,cAAIC,GACF,OAAOH,KAAKE,MAAQF,KAAKD,aAC3B,CAEQK,cAAAA,GACNJ,KAAKE,OACP,CAEQG,cAAAA,GACNL,KAAKE,OACP,CAEAI,OAAAA,GACE,OAAIN,KAAKG,YACPH,KAAKI,iBACEG,QAAQC,WAER,IAAID,QAASC,GAAYR,KAAKC,MAAMQ,KAAKD,GAEpD,CAEAE,OAAAA,GACE,MAAMC,EAAcX,KAAKC,MAAMW,QAE3BD,EAEFE,WAAWF,EAAa,GAExBX,KAAKK,gBAET,EAGF,MAAMS,EAAa,WAEnB,MAAMC,EAOJjB,WAAAA,CAAYC,EAAwB,GAACC,KAN7BgB,wBAAkB,EAAAhB,KAClBD,mBAAa,EAMnBC,KAAKgB,mBAAqB,CAAA,EAC1BhB,KAAKD,cAAgBA,CACvB,CAEQkB,oBAAAA,CAAqBC,EAAuBJ,GAClD,OAAOK,QAAQnB,KAAKgB,mBAAmBE,GACzC,CAEQE,oBAAAA,CAAqBF,EAAuBJ,GAIlD,OAHKd,KAAKiB,qBAAqBC,KAC7BlB,KAAKgB,mBAAmBE,GAAO,IAAIrB,EAAcG,KAAKD,gBAE7CC,KAACgB,mBAAmBE,EACjC,CAKQG,IAAAA,CAAKH,EAAuBJ,GAEhCd,KAAKiB,qBAAqBC,IACe,IAAzClB,KAAKoB,qBAAqBF,GAAKhB,cAExBF,KAAKgB,mBAAmBE,EAEnC,CASAf,UAAAA,CAAWe,EAAuBJ,GAChC,OAAQd,KAAKiB,qBAAqBC,IAChClB,KAAKoB,qBAAqBF,GAAKf,UACnC,CAKAG,OAAAA,CAAQY,EAAuBJ,GAC7B,OAAWd,KAACoB,qBAAqBF,GAAKZ,SACxC,CAKAI,OAAAA,CAAQQ,EAAuBJ,GAC7Bd,KAAKoB,qBAAqBF,GAAKR,UAC/BV,KAAKqB,KAAKH,EACZ,CAOAhB,KAAAA,CAAMgB,EAAuBJ,GAC3B,OAAId,KAAKiB,qBAAqBC,GACrBlB,KAAKoB,qBAAqBF,GAAKhB,MAGvC,CACH,CAMAoB,QAAAA,CAASJ,EAAuBJ,GAC9B,OAAWd,KAACE,MAAMgB,GAAO,CAC3B,CAOA,aAAMK,CACJC,EACAN,EAAuBJ,GAEvB,IAEE,aADUd,KAACM,QAAQY,SACNM,GACd,CAAA,QACCxB,KAAKU,QAAQQ,EACd,CACH,CAUA,wBAAMO,CACJD,EACAN,EAAuBJ,GAEvB,OAAId,KAAKG,WAAWe,GACPlB,KAACuB,QAAQC,EAAIN,GAEjB,IAEX"}
|
|
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\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","this","queue","count","canAcquire","incrementCount","decrementCount","acquire","priority","Promise","resolve","push","sort","a","b","release","resolveFunc","shift","setTimeout","Semaphore","semaphoreInstances","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){void 0===e&&(e=1),this.semaphoreInstances=void 0,this.maxConcurrent=void 0,this.semaphoreInstances={},this.maxConcurrent=e}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/index.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(this.maxConcurrent);\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"],"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"],"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\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","count","this","_proto","prototype","incrementCount","decrementCount","acquire","priority","_this","canAcquire","Promise","resolve","push","sort","a","b","release","resolveFunc","shift","setTimeout","get","Semaphore","semaphoreInstances","_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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"}
|
|
@@ -0,0 +1,64 @@
|
|
|
1
|
+
type KeyPrimitive = string | number;
|
|
2
|
+
type Key = KeyPrimitive | {
|
|
3
|
+
key?: KeyPrimitive;
|
|
4
|
+
};
|
|
5
|
+
type KeyOptions = Key & {
|
|
6
|
+
priority?: number;
|
|
7
|
+
};
|
|
8
|
+
declare class Semaphore {
|
|
9
|
+
private semaphoreInstances;
|
|
10
|
+
private maxConcurrent;
|
|
11
|
+
/**
|
|
12
|
+
* @param {number} [maxConcurrent] The maximum number of concurrent locks.
|
|
13
|
+
*/
|
|
14
|
+
constructor(maxConcurrent?: number);
|
|
15
|
+
private hasSemaphoreInstance;
|
|
16
|
+
private getSemaphoreInstance;
|
|
17
|
+
/**
|
|
18
|
+
* @param {string | number} [key]- Optional, the semaphore key.
|
|
19
|
+
*/
|
|
20
|
+
private tidy;
|
|
21
|
+
/**
|
|
22
|
+
* A synchronous function to determine whether a lock can be acquired.
|
|
23
|
+
*
|
|
24
|
+
* @param {string | number} [key]- Optional, the semaphore key.
|
|
25
|
+
* @returns {boolean} Returns true if the lock on `key` can be acquired, false
|
|
26
|
+
* otherwise.
|
|
27
|
+
*/
|
|
28
|
+
canAcquire(key?: Key): boolean;
|
|
29
|
+
/**
|
|
30
|
+
* @param {string | number} [key]- Optional, the semaphore key.
|
|
31
|
+
*/
|
|
32
|
+
acquire(key?: KeyOptions): Promise<void>;
|
|
33
|
+
/**
|
|
34
|
+
* @param {string | number} [key]- Optional, the semaphore key.
|
|
35
|
+
*/
|
|
36
|
+
release(key?: Key): void;
|
|
37
|
+
/**
|
|
38
|
+
* The number of active locks. Will always be less or equal to `max`.
|
|
39
|
+
*
|
|
40
|
+
* @param {string | number} [key]- Optional, the semaphore key.
|
|
41
|
+
*/
|
|
42
|
+
count(key?: Key): number;
|
|
43
|
+
/**
|
|
44
|
+
* @param {string | number} [key]- Optional, the semaphore key.
|
|
45
|
+
* @returns {boolean} True if the semaphore and key has locks, false otherwise.
|
|
46
|
+
*/
|
|
47
|
+
hasTasks(key?: Key): boolean;
|
|
48
|
+
/**
|
|
49
|
+
* @param {Function<T>} fn The function to execute.
|
|
50
|
+
* @param {string | number} [key]- Optional, the semaphore key.
|
|
51
|
+
* @returns {Promise<T>}
|
|
52
|
+
*/
|
|
53
|
+
request<T>(fn: Function, key?: KeyOptions): Promise<T>;
|
|
54
|
+
/**
|
|
55
|
+
* Asynchronously executes `fn` if a lock can be immediately acquired.
|
|
56
|
+
* Otherwise, returns null.
|
|
57
|
+
*
|
|
58
|
+
* @param {Function<T>} fn The function to execute.
|
|
59
|
+
* @param {string | number} [key]- Optional, the semaphore key.
|
|
60
|
+
* @returns {Promise<T>}
|
|
61
|
+
*/
|
|
62
|
+
requestIfAvailable<T>(fn: Function, key?: KeyOptions): Promise<T | null>;
|
|
63
|
+
}
|
|
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.1",
|
|
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,7 +23,7 @@
|
|
|
23
23
|
},
|
|
24
24
|
"devDependencies": {
|
|
25
25
|
"microbundle": "^0.15.1",
|
|
26
|
-
"vitest": "^3.
|
|
26
|
+
"vitest": "^3.2.4"
|
|
27
27
|
},
|
|
28
28
|
"keywords": [
|
|
29
29
|
"promise",
|
|
@@ -0,0 +1,46 @@
|
|
|
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
CHANGED
|
@@ -1,171 +1,2 @@
|
|
|
1
|
-
|
|
2
|
-
|
|
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(this.maxConcurrent);
|
|
70
|
-
}
|
|
71
|
-
return this.semaphoreInstances[key];
|
|
72
|
-
}
|
|
73
|
-
|
|
74
|
-
/**
|
|
75
|
-
* @param {string | number} [key]- Optional, the semaphore key.
|
|
76
|
-
*/
|
|
77
|
-
private tidy(key: string | number = defaultKey): void {
|
|
78
|
-
if (
|
|
79
|
-
this.hasSemaphoreInstance(key) &&
|
|
80
|
-
this.getSemaphoreInstance(key).count === 0
|
|
81
|
-
) {
|
|
82
|
-
delete this.semaphoreInstances[key];
|
|
83
|
-
}
|
|
84
|
-
}
|
|
85
|
-
|
|
86
|
-
/**
|
|
87
|
-
* A synchronous function to determine whether a lock can be acquired.
|
|
88
|
-
*
|
|
89
|
-
* @param {string | number} [key]- Optional, the semaphore key.
|
|
90
|
-
* @returns {boolean} Returns true if the lock on `key` can be acquired, false
|
|
91
|
-
* otherwise.
|
|
92
|
-
*/
|
|
93
|
-
canAcquire(key: string | number = defaultKey): boolean {
|
|
94
|
-
return !this.hasSemaphoreInstance(key) ||
|
|
95
|
-
this.getSemaphoreInstance(key).canAcquire;
|
|
96
|
-
}
|
|
97
|
-
|
|
98
|
-
/**
|
|
99
|
-
* @param {string | number} [key]- Optional, the semaphore key.
|
|
100
|
-
*/
|
|
101
|
-
acquire(key: string | number = defaultKey) {
|
|
102
|
-
return this.getSemaphoreInstance(key).acquire();
|
|
103
|
-
}
|
|
104
|
-
|
|
105
|
-
/**
|
|
106
|
-
* @param {string | number} [key]- Optional, the semaphore key.
|
|
107
|
-
*/
|
|
108
|
-
release(key: string | number = defaultKey): void {
|
|
109
|
-
this.getSemaphoreInstance(key).release();
|
|
110
|
-
this.tidy(key);
|
|
111
|
-
}
|
|
112
|
-
|
|
113
|
-
/**
|
|
114
|
-
* The number of active locks. Will always be less or equal to `max`.
|
|
115
|
-
*
|
|
116
|
-
* @param {string | number} [key]- Optional, the semaphore key.
|
|
117
|
-
*/
|
|
118
|
-
count(key: string | number = defaultKey): number {
|
|
119
|
-
if (this.hasSemaphoreInstance(key)) {
|
|
120
|
-
return this.getSemaphoreInstance(key).count;
|
|
121
|
-
} else {
|
|
122
|
-
return 0;
|
|
123
|
-
}
|
|
124
|
-
}
|
|
125
|
-
|
|
126
|
-
/**
|
|
127
|
-
* @param {string | number} [key]- Optional, the semaphore key.
|
|
128
|
-
* @returns {boolean} True if the semaphore and key has locks, false otherwise.
|
|
129
|
-
*/
|
|
130
|
-
hasTasks(key: string | number = defaultKey): boolean {
|
|
131
|
-
return this.count(key) > 0;
|
|
132
|
-
}
|
|
133
|
-
|
|
134
|
-
/**
|
|
135
|
-
* @param {Function<T>} fn The function to execute.
|
|
136
|
-
* @param {string | number} [key]- Optional, the semaphore key.
|
|
137
|
-
* @returns {Promise<T>}
|
|
138
|
-
*/
|
|
139
|
-
async request<T>(
|
|
140
|
-
fn: Function,
|
|
141
|
-
key: string | number = defaultKey,
|
|
142
|
-
): Promise<T> {
|
|
143
|
-
try {
|
|
144
|
-
await this.acquire(key);
|
|
145
|
-
return await fn();
|
|
146
|
-
} finally {
|
|
147
|
-
this.release(key);
|
|
148
|
-
}
|
|
149
|
-
}
|
|
150
|
-
|
|
151
|
-
/**
|
|
152
|
-
* Asynchronously executes `fn` if a lock can be immediately acquired.
|
|
153
|
-
* Otherwise, returns null.
|
|
154
|
-
*
|
|
155
|
-
* @param {Function<T>} fn The function to execute.
|
|
156
|
-
* @param {string | number} [key]- Optional, the semaphore key.
|
|
157
|
-
* @returns {Promise<T>}
|
|
158
|
-
*/
|
|
159
|
-
async requestIfAvailable<T>(
|
|
160
|
-
fn: Function,
|
|
161
|
-
key: string | number = defaultKey,
|
|
162
|
-
): Promise<T | null> {
|
|
163
|
-
if (this.canAcquire(key)) {
|
|
164
|
-
return this.request(fn, key);
|
|
165
|
-
} else {
|
|
166
|
-
return null;
|
|
167
|
-
}
|
|
168
|
-
}
|
|
169
|
-
}
|
|
170
|
-
|
|
171
|
-
export { Semaphore };
|
|
1
|
+
export { Semaphore } from "./semaphore";
|
|
2
|
+
export { GroupSemaphore } from "./group-semaphore";
|
package/src/semaphore.ts
ADDED
|
@@ -0,0 +1,200 @@
|
|
|
1
|
+
const defaultKey = "_default";
|
|
2
|
+
|
|
3
|
+
type KeyPrimitive = string | number;
|
|
4
|
+
|
|
5
|
+
type Key = KeyPrimitive | { key?: KeyPrimitive };
|
|
6
|
+
type KeyOptions = Key & { priority?: number };
|
|
7
|
+
|
|
8
|
+
const _isPrimitiveKey = (item: Key): item is KeyPrimitive =>
|
|
9
|
+
["string", "number"].includes(typeof item);
|
|
10
|
+
|
|
11
|
+
const resolveKey = (item: Key): KeyPrimitive =>
|
|
12
|
+
(_isPrimitiveKey(item) ? item : item.key) ?? defaultKey;
|
|
13
|
+
|
|
14
|
+
const resolvePriority = (item: KeyOptions) =>
|
|
15
|
+
(_isPrimitiveKey(item) ? 0 : item.priority) ?? 0;
|
|
16
|
+
|
|
17
|
+
class SemaphoreItem {
|
|
18
|
+
private queue: Array<{
|
|
19
|
+
resolve: Function;
|
|
20
|
+
priority: number;
|
|
21
|
+
}>;
|
|
22
|
+
private maxConcurrent: number;
|
|
23
|
+
|
|
24
|
+
/**
|
|
25
|
+
* The number of locks.
|
|
26
|
+
*/
|
|
27
|
+
public count: number;
|
|
28
|
+
|
|
29
|
+
constructor(maxConcurrent: number) {
|
|
30
|
+
this.queue = [];
|
|
31
|
+
this.maxConcurrent = maxConcurrent;
|
|
32
|
+
this.count = 0;
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
get canAcquire(): boolean {
|
|
36
|
+
return this.count < this.maxConcurrent;
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
private incrementCount() {
|
|
40
|
+
this.count++;
|
|
41
|
+
}
|
|
42
|
+
|
|
43
|
+
private decrementCount() {
|
|
44
|
+
this.count--;
|
|
45
|
+
}
|
|
46
|
+
|
|
47
|
+
acquire(priority: number): Promise<void> {
|
|
48
|
+
if (this.canAcquire) {
|
|
49
|
+
this.incrementCount();
|
|
50
|
+
return Promise.resolve();
|
|
51
|
+
} else {
|
|
52
|
+
return new Promise((resolve) => {
|
|
53
|
+
this.queue.push({ resolve, priority });
|
|
54
|
+
this.queue.sort((a, b) => b.priority - a.priority);
|
|
55
|
+
});
|
|
56
|
+
}
|
|
57
|
+
}
|
|
58
|
+
|
|
59
|
+
release(): void {
|
|
60
|
+
const resolveFunc = this.queue.shift();
|
|
61
|
+
|
|
62
|
+
if (resolveFunc) {
|
|
63
|
+
// Give the micro task queue a small break instead of calling resolveFunc() directly
|
|
64
|
+
setTimeout(resolveFunc.resolve, 0);
|
|
65
|
+
} else {
|
|
66
|
+
this.decrementCount();
|
|
67
|
+
}
|
|
68
|
+
}
|
|
69
|
+
}
|
|
70
|
+
|
|
71
|
+
class Semaphore {
|
|
72
|
+
private semaphoreInstances: Record<string | number, SemaphoreItem>;
|
|
73
|
+
private maxConcurrent: number;
|
|
74
|
+
|
|
75
|
+
/**
|
|
76
|
+
* @param {number} [maxConcurrent] The maximum number of concurrent locks.
|
|
77
|
+
*/
|
|
78
|
+
constructor(maxConcurrent: number = 1) {
|
|
79
|
+
this.semaphoreInstances = {};
|
|
80
|
+
this.maxConcurrent = maxConcurrent;
|
|
81
|
+
}
|
|
82
|
+
|
|
83
|
+
private hasSemaphoreInstance(key: KeyPrimitive = defaultKey) {
|
|
84
|
+
return Boolean(this.semaphoreInstances[key]);
|
|
85
|
+
}
|
|
86
|
+
|
|
87
|
+
private getSemaphoreInstance(key: KeyPrimitive = defaultKey) {
|
|
88
|
+
if (!this.hasSemaphoreInstance(key)) {
|
|
89
|
+
this.semaphoreInstances[key] = new SemaphoreItem(
|
|
90
|
+
this.maxConcurrent,
|
|
91
|
+
);
|
|
92
|
+
}
|
|
93
|
+
return this.semaphoreInstances[key];
|
|
94
|
+
}
|
|
95
|
+
|
|
96
|
+
/**
|
|
97
|
+
* @param {string | number} [key]- Optional, the semaphore key.
|
|
98
|
+
*/
|
|
99
|
+
private tidy(key: KeyPrimitive = defaultKey): void {
|
|
100
|
+
if (
|
|
101
|
+
this.hasSemaphoreInstance(key) &&
|
|
102
|
+
this.getSemaphoreInstance(key).count === 0
|
|
103
|
+
) {
|
|
104
|
+
delete this.semaphoreInstances[key];
|
|
105
|
+
}
|
|
106
|
+
}
|
|
107
|
+
|
|
108
|
+
/**
|
|
109
|
+
* A synchronous function to determine whether a lock can be acquired.
|
|
110
|
+
*
|
|
111
|
+
* @param {string | number} [key]- Optional, the semaphore key.
|
|
112
|
+
* @returns {boolean} Returns true if the lock on `key` can be acquired, false
|
|
113
|
+
* otherwise.
|
|
114
|
+
*/
|
|
115
|
+
canAcquire(key: Key = defaultKey): boolean {
|
|
116
|
+
const _key = resolveKey(key);
|
|
117
|
+
|
|
118
|
+
return !this.hasSemaphoreInstance(_key) ||
|
|
119
|
+
this.getSemaphoreInstance(_key).canAcquire;
|
|
120
|
+
}
|
|
121
|
+
|
|
122
|
+
/**
|
|
123
|
+
* @param {string | number} [key]- Optional, the semaphore key.
|
|
124
|
+
*/
|
|
125
|
+
acquire(key: KeyOptions = defaultKey) {
|
|
126
|
+
const _key = resolveKey(key);
|
|
127
|
+
const _priority = resolvePriority(key);
|
|
128
|
+
|
|
129
|
+
return this.getSemaphoreInstance(_key).acquire(_priority);
|
|
130
|
+
}
|
|
131
|
+
|
|
132
|
+
/**
|
|
133
|
+
* @param {string | number} [key]- Optional, the semaphore key.
|
|
134
|
+
*/
|
|
135
|
+
release(key: Key = defaultKey): void {
|
|
136
|
+
const _key = resolveKey(key);
|
|
137
|
+
|
|
138
|
+
this.getSemaphoreInstance(_key).release();
|
|
139
|
+
this.tidy(_key);
|
|
140
|
+
}
|
|
141
|
+
|
|
142
|
+
/**
|
|
143
|
+
* The number of active locks. Will always be less or equal to `max`.
|
|
144
|
+
*
|
|
145
|
+
* @param {string | number} [key]- Optional, the semaphore key.
|
|
146
|
+
*/
|
|
147
|
+
count(key: Key = defaultKey): number {
|
|
148
|
+
const _key = resolveKey(key);
|
|
149
|
+
|
|
150
|
+
return (this.hasSemaphoreInstance(_key))
|
|
151
|
+
? this.getSemaphoreInstance(_key).count
|
|
152
|
+
: 0;
|
|
153
|
+
}
|
|
154
|
+
|
|
155
|
+
/**
|
|
156
|
+
* @param {string | number} [key]- Optional, the semaphore key.
|
|
157
|
+
* @returns {boolean} True if the semaphore and key has locks, false otherwise.
|
|
158
|
+
*/
|
|
159
|
+
hasTasks(key: Key = defaultKey): boolean {
|
|
160
|
+
return this.count(key) > 0;
|
|
161
|
+
}
|
|
162
|
+
|
|
163
|
+
/**
|
|
164
|
+
* @param {Function<T>} fn The function to execute.
|
|
165
|
+
* @param {string | number} [key]- Optional, the semaphore key.
|
|
166
|
+
* @returns {Promise<T>}
|
|
167
|
+
*/
|
|
168
|
+
async request<T>(
|
|
169
|
+
fn: Function,
|
|
170
|
+
key: KeyOptions = defaultKey,
|
|
171
|
+
): Promise<T> {
|
|
172
|
+
try {
|
|
173
|
+
await this.acquire(key);
|
|
174
|
+
return await fn();
|
|
175
|
+
} finally {
|
|
176
|
+
this.release(key);
|
|
177
|
+
}
|
|
178
|
+
}
|
|
179
|
+
|
|
180
|
+
/**
|
|
181
|
+
* Asynchronously executes `fn` if a lock can be immediately acquired.
|
|
182
|
+
* Otherwise, returns null.
|
|
183
|
+
*
|
|
184
|
+
* @param {Function<T>} fn The function to execute.
|
|
185
|
+
* @param {string | number} [key]- Optional, the semaphore key.
|
|
186
|
+
* @returns {Promise<T>}
|
|
187
|
+
*/
|
|
188
|
+
async requestIfAvailable<T>(
|
|
189
|
+
fn: Function,
|
|
190
|
+
key: KeyOptions = defaultKey,
|
|
191
|
+
): Promise<T | null> {
|
|
192
|
+
if (this.canAcquire(key)) {
|
|
193
|
+
return this.request(fn, key);
|
|
194
|
+
} else {
|
|
195
|
+
return null;
|
|
196
|
+
}
|
|
197
|
+
}
|
|
198
|
+
}
|
|
199
|
+
|
|
200
|
+
export { Semaphore };
|