@chriscdn/promise-semaphore 1.0.4 → 1.0.9
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/.prettierrc +4 -0
- package/LICENSE +1 -1
- package/README.md +140 -9
- package/lib/index.cjs.js +92 -65
- package/lib/index.es.js +93 -66
- package/package.json +21 -20
- package/src/index.js +93 -66
package/.prettierrc
ADDED
package/LICENSE
CHANGED
package/README.md
CHANGED
|
@@ -33,7 +33,7 @@ The `maxConcurrent` parameter is optional, and defaults to `1` (making it an exc
|
|
|
33
33
|
semaphore.acquire([key])
|
|
34
34
|
```
|
|
35
35
|
|
|
36
|
-
This returns a `Promise`, which resolves once a lock has been acquired. The `key` parameter is optional and permits the same `Semaphore` instance to be used in different contexts.
|
|
36
|
+
This returns a `Promise`, which resolves once a lock has been acquired. The `key` parameter is optional and permits the same `Semaphore` instance to be used in different contexts. See the second example.
|
|
37
37
|
|
|
38
38
|
### Release a lock
|
|
39
39
|
|
|
@@ -41,7 +41,52 @@ This returns a `Promise`, which resolves once a lock has been acquired. The `ke
|
|
|
41
41
|
semaphore.release([key])
|
|
42
42
|
```
|
|
43
43
|
|
|
44
|
-
|
|
44
|
+
The `release` call should be executed from a `finally` block (whether using promises or a try/catch block) to guarantee it gets called.
|
|
45
|
+
|
|
46
|
+
### Check if a lock can be acquired
|
|
47
|
+
|
|
48
|
+
```js
|
|
49
|
+
semaphore.canAcquire([key])
|
|
50
|
+
```
|
|
51
|
+
|
|
52
|
+
This method is synchronous, and returns `true` if a lock can be immediately acquired, `false` otherwise.
|
|
53
|
+
|
|
54
|
+
### request function
|
|
55
|
+
|
|
56
|
+
```js
|
|
57
|
+
const results = await semaphore.request(fn [,key])
|
|
58
|
+
```
|
|
59
|
+
|
|
60
|
+
This function reduces boilerplate when using `acquire` and `release`. It returns a promise, which resolves once `fn` has completed. It is functionally equivalent to:
|
|
61
|
+
|
|
62
|
+
```js
|
|
63
|
+
try {
|
|
64
|
+
await semaphore.acquire([key])
|
|
65
|
+
const results = await fn()
|
|
66
|
+
} finally {
|
|
67
|
+
semaphore.release([key])
|
|
68
|
+
}
|
|
69
|
+
```
|
|
70
|
+
|
|
71
|
+
See the examples below.
|
|
72
|
+
|
|
73
|
+
### requestIfAvailable function
|
|
74
|
+
|
|
75
|
+
```js
|
|
76
|
+
const results = await semaphore.requestIfAvailable(fn [,key])
|
|
77
|
+
```
|
|
78
|
+
|
|
79
|
+
This is functionally equivalent to:
|
|
80
|
+
|
|
81
|
+
```js
|
|
82
|
+
const results = semaphore.canAcquire([key] ?
|
|
83
|
+
await semaphore.request(fn, [key]) :
|
|
84
|
+
null
|
|
85
|
+
```
|
|
86
|
+
|
|
87
|
+
This is useful in situations where only one instance of a function block should run at a time, while discarding other attempts to execute the block. E.g., a button that is being repeatedly tapped or clicked by the user.
|
|
88
|
+
|
|
89
|
+
## Example 1
|
|
45
90
|
|
|
46
91
|
```js
|
|
47
92
|
const Semaphore = require('@chriscdn/promise-semaphore')
|
|
@@ -50,23 +95,109 @@ const semaphore = new Semaphore()
|
|
|
50
95
|
// using promises
|
|
51
96
|
semaphore.acquire()
|
|
52
97
|
.then(() => {
|
|
53
|
-
// This block executes once a lock has been acquired. If already
|
|
54
|
-
// then this block will wait and execute once all
|
|
55
|
-
// released.
|
|
98
|
+
// This block executes once a lock has been acquired. If already
|
|
99
|
+
// locked then this block will wait and execute once all preceeding
|
|
100
|
+
// locks have been released.
|
|
101
|
+
|
|
102
|
+
// do your critical stuff here
|
|
103
|
+
|
|
56
104
|
})
|
|
57
105
|
.finally(() => {
|
|
58
|
-
// release the lock permitting the next queued
|
|
106
|
+
// release the lock permitting the next queued block to continue
|
|
59
107
|
semaphore.release()
|
|
60
108
|
})
|
|
61
109
|
|
|
62
|
-
// or, using async/await
|
|
63
|
-
await semaphore.acquire()
|
|
64
110
|
|
|
111
|
+
// or, using async/await
|
|
65
112
|
try {
|
|
66
|
-
|
|
113
|
+
await semaphore.acquire()
|
|
114
|
+
|
|
115
|
+
// do your critical stuff here
|
|
116
|
+
|
|
67
117
|
} finally {
|
|
68
118
|
semaphore.release()
|
|
69
119
|
}
|
|
120
|
+
|
|
121
|
+
|
|
122
|
+
// or, using the request function
|
|
123
|
+
semaphore.request(() => {
|
|
124
|
+
|
|
125
|
+
// do your critical stuff here
|
|
126
|
+
|
|
127
|
+
})
|
|
128
|
+
|
|
129
|
+
```
|
|
130
|
+
|
|
131
|
+
## Example 2
|
|
132
|
+
|
|
133
|
+
Say you have an asynchronous function to download a file and save it to disk
|
|
134
|
+
|
|
135
|
+
```js
|
|
136
|
+
async function downloadAndSave(url) {
|
|
137
|
+
|
|
138
|
+
const filePath = urlToFilePath(url)
|
|
139
|
+
|
|
140
|
+
if (await pathExists(filePath)) {
|
|
141
|
+
// the file is on disk, so no action is required
|
|
142
|
+
} else {
|
|
143
|
+
await downloadToFile(url, filePath)
|
|
144
|
+
}
|
|
145
|
+
|
|
146
|
+
return filePath
|
|
147
|
+
}
|
|
148
|
+
```
|
|
149
|
+
|
|
150
|
+
This works until a process calls `downloadAndSave()` in short succession with the same `url` parameter. This can cause multiple simultaneous downloads that attempt to write to the same file.
|
|
151
|
+
|
|
152
|
+
This can be resolved with a `Semaphore` instance using the `key` parameter:
|
|
153
|
+
|
|
154
|
+
```js
|
|
155
|
+
const Semaphore = require('@chriscdn/promise-semaphore')
|
|
156
|
+
const semaphore = new Semaphore()
|
|
157
|
+
|
|
158
|
+
async function downloadAndSave(url) {
|
|
159
|
+
|
|
160
|
+
try {
|
|
161
|
+
|
|
162
|
+
await semaphore.acquire(url)
|
|
163
|
+
|
|
164
|
+
// This block continues once a lock on url is acquired. This permits
|
|
165
|
+
// multiple simulataneous downloads for each unique url.
|
|
166
|
+
|
|
167
|
+
const filePath = urlToFilePath(url)
|
|
168
|
+
|
|
169
|
+
if (await pathExists(filePath)) {
|
|
170
|
+
// the file is on disk, so no action is required
|
|
171
|
+
} else {
|
|
172
|
+
await downloadToFile(url, filePath)
|
|
173
|
+
}
|
|
174
|
+
|
|
175
|
+
return filePath
|
|
176
|
+
|
|
177
|
+
} finally {
|
|
178
|
+
semaphore.release(url)
|
|
179
|
+
}
|
|
180
|
+
}
|
|
181
|
+
```
|
|
182
|
+
|
|
183
|
+
Alternatively, this can be accomplished with the `request` function:
|
|
184
|
+
|
|
185
|
+
```js
|
|
186
|
+
async function downloadAndSave(url) {
|
|
187
|
+
|
|
188
|
+
return semaphore.request(() => {
|
|
189
|
+
const filePath = urlToFilePath(url)
|
|
190
|
+
|
|
191
|
+
if (await pathExists(filePath)) {
|
|
192
|
+
// the file is on disk, so no action is required
|
|
193
|
+
} else {
|
|
194
|
+
await downloadToFile(url, filePath)
|
|
195
|
+
}
|
|
196
|
+
|
|
197
|
+
return filePath
|
|
198
|
+
}, url)
|
|
199
|
+
|
|
200
|
+
}
|
|
70
201
|
```
|
|
71
202
|
|
|
72
203
|
## License
|
package/lib/index.cjs.js
CHANGED
|
@@ -1,76 +1,103 @@
|
|
|
1
1
|
'use strict';
|
|
2
2
|
|
|
3
3
|
class SemaphoreItem {
|
|
4
|
-
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
4
|
+
constructor(max) {
|
|
5
|
+
this.queue = [];
|
|
6
|
+
this.max = max;
|
|
7
|
+
this.count = 0;
|
|
8
|
+
}
|
|
9
|
+
|
|
10
|
+
get canAcquire() {
|
|
11
|
+
return this.count < this.max
|
|
12
|
+
}
|
|
13
|
+
|
|
14
|
+
acquire() {
|
|
15
|
+
if (this.canAcquire) {
|
|
16
|
+
this.count++;
|
|
17
|
+
return Promise.resolve()
|
|
18
|
+
} else {
|
|
19
|
+
return new Promise((resolve) => {
|
|
20
|
+
this.queue.push(resolve);
|
|
21
|
+
})
|
|
22
|
+
}
|
|
23
|
+
}
|
|
24
|
+
|
|
25
|
+
release() {
|
|
26
|
+
const resolveFunc = this.queue.shift();
|
|
27
|
+
|
|
28
|
+
if (resolveFunc) {
|
|
29
|
+
// Give the micro task queue a small break instead of calling resolveFunc() directly
|
|
30
|
+
setTimeout(resolveFunc, 0);
|
|
31
|
+
} else {
|
|
32
|
+
this.count--;
|
|
33
|
+
}
|
|
34
|
+
}
|
|
31
35
|
}
|
|
32
36
|
|
|
33
37
|
const defaultKey = '_default';
|
|
34
38
|
|
|
35
39
|
class Semaphore {
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
40
|
+
constructor(max = 1) {
|
|
41
|
+
this.semaphoreItems = {};
|
|
42
|
+
this.max = max;
|
|
43
|
+
}
|
|
44
|
+
|
|
45
|
+
_getSemaphoreInstance(key = defaultKey) {
|
|
46
|
+
if (!this.semaphoreItems[key]) {
|
|
47
|
+
this.semaphoreItems[key] = new SemaphoreItem(this.max);
|
|
48
|
+
}
|
|
49
|
+
return this.semaphoreItems[key]
|
|
50
|
+
}
|
|
51
|
+
|
|
52
|
+
_tidy(key = defaultKey) {
|
|
53
|
+
if (this._getSemaphoreInstance(key).count == 0) {
|
|
54
|
+
delete this.semaphoreItems[key];
|
|
55
|
+
}
|
|
56
|
+
}
|
|
57
|
+
|
|
58
|
+
canAcquire(key = defaultKey) {
|
|
59
|
+
return this._getSemaphoreInstance(key).canAcquire
|
|
60
|
+
}
|
|
61
|
+
|
|
62
|
+
acquire(key = defaultKey) {
|
|
63
|
+
return this._getSemaphoreInstance(key).acquire()
|
|
64
|
+
}
|
|
65
|
+
|
|
66
|
+
release(key = defaultKey) {
|
|
67
|
+
this._getSemaphoreInstance(key).release();
|
|
68
|
+
this._tidy(key);
|
|
69
|
+
}
|
|
70
|
+
|
|
71
|
+
count(key = defaultKey) {
|
|
72
|
+
if (this.semaphoreItems[key]) {
|
|
73
|
+
return this.semaphoreItems[key].count
|
|
74
|
+
} else {
|
|
75
|
+
return 0
|
|
76
|
+
}
|
|
77
|
+
}
|
|
78
|
+
|
|
79
|
+
hasTasks(key = defaultKey) {
|
|
80
|
+
return this.count(key) > 0
|
|
81
|
+
}
|
|
82
|
+
|
|
83
|
+
async request(fn, key = defaultKey) {
|
|
84
|
+
try {
|
|
85
|
+
await this.acquire(key);
|
|
86
|
+
return await fn()
|
|
87
|
+
} finally {
|
|
88
|
+
this.release(key);
|
|
89
|
+
}
|
|
90
|
+
}
|
|
91
|
+
|
|
92
|
+
async requestIfAvailable(fn, key = defaultKey) {
|
|
93
|
+
if (this.canAcquire(key)) {
|
|
94
|
+
return this.request(fn, key)
|
|
95
|
+
} else {
|
|
96
|
+
// Use canAcquire if you need to know if a function will be dismissed due
|
|
97
|
+
// to an existing lock.
|
|
98
|
+
return null
|
|
99
|
+
}
|
|
100
|
+
}
|
|
74
101
|
}
|
|
75
102
|
|
|
76
103
|
module.exports = Semaphore;
|
package/lib/index.es.js
CHANGED
|
@@ -1,76 +1,103 @@
|
|
|
1
1
|
class SemaphoreItem {
|
|
2
|
-
|
|
3
|
-
|
|
4
|
-
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
2
|
+
constructor(max) {
|
|
3
|
+
this.queue = [];
|
|
4
|
+
this.max = max;
|
|
5
|
+
this.count = 0;
|
|
6
|
+
}
|
|
7
|
+
|
|
8
|
+
get canAcquire() {
|
|
9
|
+
return this.count < this.max
|
|
10
|
+
}
|
|
11
|
+
|
|
12
|
+
acquire() {
|
|
13
|
+
if (this.canAcquire) {
|
|
14
|
+
this.count++;
|
|
15
|
+
return Promise.resolve()
|
|
16
|
+
} else {
|
|
17
|
+
return new Promise((resolve) => {
|
|
18
|
+
this.queue.push(resolve);
|
|
19
|
+
})
|
|
20
|
+
}
|
|
21
|
+
}
|
|
22
|
+
|
|
23
|
+
release() {
|
|
24
|
+
const resolveFunc = this.queue.shift();
|
|
25
|
+
|
|
26
|
+
if (resolveFunc) {
|
|
27
|
+
// Give the micro task queue a small break instead of calling resolveFunc() directly
|
|
28
|
+
setTimeout(resolveFunc, 0);
|
|
29
|
+
} else {
|
|
30
|
+
this.count--;
|
|
31
|
+
}
|
|
32
|
+
}
|
|
29
33
|
}
|
|
30
34
|
|
|
31
35
|
const defaultKey = '_default';
|
|
32
36
|
|
|
33
37
|
class Semaphore {
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
38
|
+
constructor(max = 1) {
|
|
39
|
+
this.semaphoreItems = {};
|
|
40
|
+
this.max = max;
|
|
41
|
+
}
|
|
42
|
+
|
|
43
|
+
_getSemaphoreInstance(key = defaultKey) {
|
|
44
|
+
if (!this.semaphoreItems[key]) {
|
|
45
|
+
this.semaphoreItems[key] = new SemaphoreItem(this.max);
|
|
46
|
+
}
|
|
47
|
+
return this.semaphoreItems[key]
|
|
48
|
+
}
|
|
49
|
+
|
|
50
|
+
_tidy(key = defaultKey) {
|
|
51
|
+
if (this._getSemaphoreInstance(key).count == 0) {
|
|
52
|
+
delete this.semaphoreItems[key];
|
|
53
|
+
}
|
|
54
|
+
}
|
|
55
|
+
|
|
56
|
+
canAcquire(key = defaultKey) {
|
|
57
|
+
return this._getSemaphoreInstance(key).canAcquire
|
|
58
|
+
}
|
|
59
|
+
|
|
60
|
+
acquire(key = defaultKey) {
|
|
61
|
+
return this._getSemaphoreInstance(key).acquire()
|
|
62
|
+
}
|
|
63
|
+
|
|
64
|
+
release(key = defaultKey) {
|
|
65
|
+
this._getSemaphoreInstance(key).release();
|
|
66
|
+
this._tidy(key);
|
|
67
|
+
}
|
|
68
|
+
|
|
69
|
+
count(key = defaultKey) {
|
|
70
|
+
if (this.semaphoreItems[key]) {
|
|
71
|
+
return this.semaphoreItems[key].count
|
|
72
|
+
} else {
|
|
73
|
+
return 0
|
|
74
|
+
}
|
|
75
|
+
}
|
|
76
|
+
|
|
77
|
+
hasTasks(key = defaultKey) {
|
|
78
|
+
return this.count(key) > 0
|
|
79
|
+
}
|
|
80
|
+
|
|
81
|
+
async request(fn, key = defaultKey) {
|
|
82
|
+
try {
|
|
83
|
+
await this.acquire(key);
|
|
84
|
+
return await fn()
|
|
85
|
+
} finally {
|
|
86
|
+
this.release(key);
|
|
87
|
+
}
|
|
88
|
+
}
|
|
89
|
+
|
|
90
|
+
async requestIfAvailable(fn, key = defaultKey) {
|
|
91
|
+
if (this.canAcquire(key)) {
|
|
92
|
+
return this.request(fn, key)
|
|
93
|
+
} else {
|
|
94
|
+
// Use canAcquire if you need to know if a function will be dismissed due
|
|
95
|
+
// to an existing lock.
|
|
96
|
+
return null
|
|
97
|
+
}
|
|
98
|
+
}
|
|
72
99
|
}
|
|
73
100
|
|
|
74
101
|
var src = Semaphore;
|
|
75
102
|
|
|
76
|
-
export default
|
|
103
|
+
export { src as default };
|
package/package.json
CHANGED
|
@@ -1,22 +1,23 @@
|
|
|
1
1
|
{
|
|
2
|
-
|
|
3
|
-
|
|
4
|
-
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
2
|
+
"name": "@chriscdn/promise-semaphore",
|
|
3
|
+
"version": "1.0.9",
|
|
4
|
+
"description": "Limit or throttle the simultaneous execution of asynchronous code in separate iterations of the event loop.",
|
|
5
|
+
"main": "lib/index.cjs.js",
|
|
6
|
+
"module": "lib/index.es.js",
|
|
7
|
+
"repository": "https://github.com/chriscdn/promise-semaphore",
|
|
8
|
+
"author": "Christopher Meyer <chris@schwiiz.org>",
|
|
9
|
+
"license": "MIT",
|
|
10
|
+
"scripts": {
|
|
11
|
+
"build": "rollup -c"
|
|
12
|
+
},
|
|
13
|
+
"devDependencies": {
|
|
14
|
+
"@rollup/plugin-commonjs": "^21.0.2"
|
|
15
|
+
},
|
|
16
|
+
"keywords": [
|
|
17
|
+
"promise",
|
|
18
|
+
"semaphore",
|
|
19
|
+
"lock",
|
|
20
|
+
"mutex",
|
|
21
|
+
"async"
|
|
22
|
+
]
|
|
22
23
|
}
|
package/src/index.js
CHANGED
|
@@ -1,74 +1,101 @@
|
|
|
1
1
|
class SemaphoreItem {
|
|
2
|
-
|
|
3
|
-
|
|
4
|
-
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
2
|
+
constructor(max) {
|
|
3
|
+
this.queue = []
|
|
4
|
+
this.max = max
|
|
5
|
+
this.count = 0
|
|
6
|
+
}
|
|
7
|
+
|
|
8
|
+
get canAcquire() {
|
|
9
|
+
return this.count < this.max
|
|
10
|
+
}
|
|
11
|
+
|
|
12
|
+
acquire() {
|
|
13
|
+
if (this.canAcquire) {
|
|
14
|
+
this.count++
|
|
15
|
+
return Promise.resolve()
|
|
16
|
+
} else {
|
|
17
|
+
return new Promise((resolve) => {
|
|
18
|
+
this.queue.push(resolve)
|
|
19
|
+
})
|
|
20
|
+
}
|
|
21
|
+
}
|
|
22
|
+
|
|
23
|
+
release() {
|
|
24
|
+
const resolveFunc = this.queue.shift()
|
|
25
|
+
|
|
26
|
+
if (resolveFunc) {
|
|
27
|
+
// Give the micro task queue a small break instead of calling resolveFunc() directly
|
|
28
|
+
setTimeout(resolveFunc, 0)
|
|
29
|
+
} else {
|
|
30
|
+
this.count--
|
|
31
|
+
}
|
|
32
|
+
}
|
|
29
33
|
}
|
|
30
34
|
|
|
31
35
|
const defaultKey = '_default'
|
|
32
36
|
|
|
33
37
|
class Semaphore {
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
38
|
+
constructor(max = 1) {
|
|
39
|
+
this.semaphoreItems = {}
|
|
40
|
+
this.max = max
|
|
41
|
+
}
|
|
42
|
+
|
|
43
|
+
_getSemaphoreInstance(key = defaultKey) {
|
|
44
|
+
if (!this.semaphoreItems[key]) {
|
|
45
|
+
this.semaphoreItems[key] = new SemaphoreItem(this.max)
|
|
46
|
+
}
|
|
47
|
+
return this.semaphoreItems[key]
|
|
48
|
+
}
|
|
49
|
+
|
|
50
|
+
_tidy(key = defaultKey) {
|
|
51
|
+
if (this._getSemaphoreInstance(key).count == 0) {
|
|
52
|
+
delete this.semaphoreItems[key]
|
|
53
|
+
}
|
|
54
|
+
}
|
|
55
|
+
|
|
56
|
+
canAcquire(key = defaultKey) {
|
|
57
|
+
return this._getSemaphoreInstance(key).canAcquire
|
|
58
|
+
}
|
|
59
|
+
|
|
60
|
+
acquire(key = defaultKey) {
|
|
61
|
+
return this._getSemaphoreInstance(key).acquire()
|
|
62
|
+
}
|
|
63
|
+
|
|
64
|
+
release(key = defaultKey) {
|
|
65
|
+
this._getSemaphoreInstance(key).release()
|
|
66
|
+
this._tidy(key)
|
|
67
|
+
}
|
|
68
|
+
|
|
69
|
+
count(key = defaultKey) {
|
|
70
|
+
if (this.semaphoreItems[key]) {
|
|
71
|
+
return this.semaphoreItems[key].count
|
|
72
|
+
} else {
|
|
73
|
+
return 0
|
|
74
|
+
}
|
|
75
|
+
}
|
|
76
|
+
|
|
77
|
+
hasTasks(key = defaultKey) {
|
|
78
|
+
return this.count(key) > 0
|
|
79
|
+
}
|
|
80
|
+
|
|
81
|
+
async request(fn, key = defaultKey) {
|
|
82
|
+
try {
|
|
83
|
+
await this.acquire(key)
|
|
84
|
+
return await fn()
|
|
85
|
+
} finally {
|
|
86
|
+
this.release(key)
|
|
87
|
+
}
|
|
88
|
+
}
|
|
89
|
+
|
|
90
|
+
async requestIfAvailable(fn, key = defaultKey) {
|
|
91
|
+
if (this.canAcquire(key)) {
|
|
92
|
+
return this.request(fn, key)
|
|
93
|
+
} else {
|
|
94
|
+
// Use canAcquire if you need to know if a function will be dismissed due
|
|
95
|
+
// to an existing lock.
|
|
96
|
+
return null
|
|
97
|
+
}
|
|
98
|
+
}
|
|
72
99
|
}
|
|
73
100
|
|
|
74
|
-
module.exports = Semaphore
|
|
101
|
+
module.exports = Semaphore
|