@ls-stack/utils 3.38.0 → 3.39.0
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/dist/asyncQueue.cjs +425 -13
- package/dist/asyncQueue.d.cts +427 -5
- package/dist/asyncQueue.d.ts +427 -5
- package/dist/asyncQueue.js +414 -13
- package/docs/asyncQueue/-internal-.md +88 -606
- package/docs/asyncQueue/README.md +1414 -4
- package/package.json +1 -1
package/dist/asyncQueue.js
CHANGED
|
@@ -1,6 +1,11 @@
|
|
|
1
1
|
import {
|
|
2
2
|
defer
|
|
3
3
|
} from "./chunk-DFXNVEH6.js";
|
|
4
|
+
import {
|
|
5
|
+
durationObjToMs
|
|
6
|
+
} from "./chunk-5MNYPLZI.js";
|
|
7
|
+
import "./chunk-HTCYUMDR.js";
|
|
8
|
+
import "./chunk-II4R3VVX.js";
|
|
4
9
|
|
|
5
10
|
// src/asyncQueue.ts
|
|
6
11
|
import { evtmitter } from "evtmitter";
|
|
@@ -10,7 +15,7 @@ import {
|
|
|
10
15
|
resultify,
|
|
11
16
|
unknownToError
|
|
12
17
|
} from "t-result";
|
|
13
|
-
var AsyncQueue = class {
|
|
18
|
+
var AsyncQueue = class _AsyncQueue {
|
|
14
19
|
#queue = [];
|
|
15
20
|
#pending = 0;
|
|
16
21
|
#size = 0;
|
|
@@ -18,19 +23,61 @@ var AsyncQueue = class {
|
|
|
18
23
|
#completed = 0;
|
|
19
24
|
#failed = 0;
|
|
20
25
|
#idleResolvers = [];
|
|
26
|
+
#sizeLessThanWaiters = [];
|
|
27
|
+
/**
|
|
28
|
+
* Event emitter for tracking task lifecycle
|
|
29
|
+
*
|
|
30
|
+
* @example Listening to Events
|
|
31
|
+
* ```typescript
|
|
32
|
+
* const queue = createAsyncQueue<string>();
|
|
33
|
+
*
|
|
34
|
+
* queue.events.on('start', (event) => {
|
|
35
|
+
* console.log('Task started:', event.payload.meta);
|
|
36
|
+
* });
|
|
37
|
+
*
|
|
38
|
+
* queue.events.on('complete', (event) => {
|
|
39
|
+
* console.log('Task completed:', event.payload.value);
|
|
40
|
+
* });
|
|
41
|
+
*
|
|
42
|
+
* queue.events.on('error', (event) => {
|
|
43
|
+
* console.error('Task failed:', event.payload.error);
|
|
44
|
+
* });
|
|
45
|
+
* ```
|
|
46
|
+
*/
|
|
21
47
|
events = evtmitter();
|
|
22
48
|
#signal;
|
|
23
49
|
#taskTimeout;
|
|
50
|
+
#stopped = false;
|
|
51
|
+
#paused = false;
|
|
52
|
+
#started = false;
|
|
53
|
+
#stopOnError = false;
|
|
54
|
+
#rejectPendingOnError = false;
|
|
55
|
+
#autoStart = true;
|
|
56
|
+
#stoppedReason;
|
|
57
|
+
#rateLimit;
|
|
58
|
+
#taskExecutionTimes = [];
|
|
59
|
+
#rateLimitTimeouts = /* @__PURE__ */ new Set();
|
|
60
|
+
/** Array of all task failures with metadata for debugging and analysis */
|
|
24
61
|
failures = [];
|
|
62
|
+
/** Array of all task completions with metadata for debugging and analysis */
|
|
25
63
|
completions = [];
|
|
26
64
|
constructor({
|
|
27
65
|
concurrency = 1,
|
|
28
66
|
signal,
|
|
29
|
-
timeout: taskTimeout
|
|
67
|
+
timeout: taskTimeout,
|
|
68
|
+
stopOnError = false,
|
|
69
|
+
rejectPendingOnError = false,
|
|
70
|
+
autoStart = true,
|
|
71
|
+
rateLimit
|
|
30
72
|
} = {}) {
|
|
31
73
|
this.#concurrency = concurrency;
|
|
32
74
|
this.#signal = signal;
|
|
33
75
|
this.#taskTimeout = taskTimeout;
|
|
76
|
+
this.#stopOnError = stopOnError;
|
|
77
|
+
this.#rejectPendingOnError = rejectPendingOnError;
|
|
78
|
+
this.#autoStart = autoStart;
|
|
79
|
+
this.#started = autoStart;
|
|
80
|
+
this.#rateLimit = rateLimit;
|
|
34
81
|
this.events.on("error", (e) => {
|
|
35
82
|
this.failures.push(e.payload);
|
|
36
83
|
});
|
|
@@ -38,14 +85,113 @@ var AsyncQueue = class {
|
|
|
38
85
|
this.completions.push(e.payload);
|
|
39
86
|
});
|
|
40
87
|
}
|
|
88
|
+
#getRateLimitIntervalMs() {
|
|
89
|
+
if (!this.#rateLimit) return 0;
|
|
90
|
+
return typeof this.#rateLimit.interval === "number" ? this.#rateLimit.interval : durationObjToMs(this.#rateLimit.interval);
|
|
91
|
+
}
|
|
92
|
+
#cleanupExpiredExecutionTimes(now) {
|
|
93
|
+
if (!this.#rateLimit) return;
|
|
94
|
+
const intervalMs = this.#getRateLimitIntervalMs();
|
|
95
|
+
const cutoff = now - intervalMs;
|
|
96
|
+
this.#taskExecutionTimes = this.#taskExecutionTimes.filter(
|
|
97
|
+
(time) => time > cutoff
|
|
98
|
+
);
|
|
99
|
+
}
|
|
100
|
+
#isRateLimited() {
|
|
101
|
+
if (!this.#rateLimit) return false;
|
|
102
|
+
const now = Date.now();
|
|
103
|
+
this.#cleanupExpiredExecutionTimes(now);
|
|
104
|
+
return this.#taskExecutionTimes.length >= this.#rateLimit.maxTasks;
|
|
105
|
+
}
|
|
106
|
+
#getRateLimitDelay() {
|
|
107
|
+
if (!this.#rateLimit || this.#taskExecutionTimes.length === 0) return 0;
|
|
108
|
+
const oldestExecution = this.#taskExecutionTimes[0];
|
|
109
|
+
if (oldestExecution === void 0) return 0;
|
|
110
|
+
const intervalMs = this.#getRateLimitIntervalMs();
|
|
111
|
+
const timeUntilSlotOpens = oldestExecution + intervalMs - Date.now();
|
|
112
|
+
return Math.max(0, timeUntilSlotOpens);
|
|
113
|
+
}
|
|
114
|
+
#recordTaskExecution() {
|
|
115
|
+
if (!this.#rateLimit) return;
|
|
116
|
+
const now = Date.now();
|
|
117
|
+
this.#taskExecutionTimes.push(now);
|
|
118
|
+
this.#cleanupExpiredExecutionTimes(now);
|
|
119
|
+
}
|
|
41
120
|
#enqueue(task) {
|
|
42
121
|
this.#queue.push(task);
|
|
43
122
|
this.#size++;
|
|
44
123
|
}
|
|
124
|
+
static #createTimeoutSignal(ms) {
|
|
125
|
+
const controller = new AbortController();
|
|
126
|
+
const id = setTimeout(() => {
|
|
127
|
+
controller.abort(
|
|
128
|
+
new DOMException(
|
|
129
|
+
"The operation was aborted due to timeout",
|
|
130
|
+
"TimeoutError"
|
|
131
|
+
)
|
|
132
|
+
);
|
|
133
|
+
}, ms);
|
|
134
|
+
controller.signal.addEventListener(
|
|
135
|
+
"abort",
|
|
136
|
+
() => {
|
|
137
|
+
clearTimeout(id);
|
|
138
|
+
},
|
|
139
|
+
{ once: true }
|
|
140
|
+
);
|
|
141
|
+
return controller.signal;
|
|
142
|
+
}
|
|
143
|
+
// removed: onEmpty-related waiters
|
|
144
|
+
#resolveSizeLessThanWaiters() {
|
|
145
|
+
if (this.#sizeLessThanWaiters.length === 0) return;
|
|
146
|
+
const remaining = [];
|
|
147
|
+
for (const waiter of this.#sizeLessThanWaiters) {
|
|
148
|
+
if (this.#size < waiter.limit) {
|
|
149
|
+
waiter.resolve();
|
|
150
|
+
} else {
|
|
151
|
+
remaining.push(waiter);
|
|
152
|
+
}
|
|
153
|
+
}
|
|
154
|
+
this.#sizeLessThanWaiters = remaining;
|
|
155
|
+
}
|
|
156
|
+
/**
|
|
157
|
+
* Add a task that returns a Result to the queue
|
|
158
|
+
*
|
|
159
|
+
* Use this method when your task function already returns a Result type.
|
|
160
|
+
* For functions that throw errors or return plain values, use `resultifyAdd` instead.
|
|
161
|
+
*
|
|
162
|
+
* @param fn - Task function that returns a Result
|
|
163
|
+
* @param options - Optional configuration for this task
|
|
164
|
+
* @returns Promise that resolves with the task result
|
|
165
|
+
*
|
|
166
|
+
* @example
|
|
167
|
+
* ```typescript
|
|
168
|
+
* const queue = createAsyncQueue<string>();
|
|
169
|
+
*
|
|
170
|
+
* const result = await queue.add(async () => {
|
|
171
|
+
* try {
|
|
172
|
+
* const data = await fetchData();
|
|
173
|
+
* return Result.ok(data);
|
|
174
|
+
* } catch (error) {
|
|
175
|
+
* return Result.err(error);
|
|
176
|
+
* }
|
|
177
|
+
* });
|
|
178
|
+
*
|
|
179
|
+
* if (result.ok) {
|
|
180
|
+
* console.log('Success:', result.value);
|
|
181
|
+
* } else {
|
|
182
|
+
* console.log('Error:', result.error);
|
|
183
|
+
* }
|
|
184
|
+
* ```
|
|
185
|
+
*/
|
|
45
186
|
async add(fn, options) {
|
|
46
187
|
if (this.#signal?.aborted) {
|
|
47
188
|
return Result.err(
|
|
48
|
-
this.#signal.reason instanceof Error ? this.#signal.reason : new DOMException("
|
|
189
|
+
this.#signal.reason instanceof Error ? this.#signal.reason : new DOMException("This operation was aborted", "AbortError")
|
|
190
|
+
);
|
|
191
|
+
}
|
|
192
|
+
if (this.#stopped) {
|
|
193
|
+
return Result.err(
|
|
194
|
+
this.#stoppedReason ?? new Error("Queue has been stopped")
|
|
49
195
|
);
|
|
50
196
|
}
|
|
51
197
|
const deferred = defer();
|
|
@@ -61,7 +207,9 @@ var AsyncQueue = class {
|
|
|
61
207
|
timeout: taskTimeout
|
|
62
208
|
};
|
|
63
209
|
this.#enqueue(task);
|
|
64
|
-
this.#
|
|
210
|
+
if (this.#autoStart && this.#started) {
|
|
211
|
+
this.#processQueue();
|
|
212
|
+
}
|
|
65
213
|
const r = await deferred.promise;
|
|
66
214
|
if (options?.onComplete) {
|
|
67
215
|
r.onOk(options.onComplete);
|
|
@@ -71,6 +219,44 @@ var AsyncQueue = class {
|
|
|
71
219
|
}
|
|
72
220
|
return r;
|
|
73
221
|
}
|
|
222
|
+
/**
|
|
223
|
+
* Add a task that returns a plain value or throws errors to the queue
|
|
224
|
+
*
|
|
225
|
+
* This is the most commonly used method. It automatically wraps your function
|
|
226
|
+
* to handle errors and convert them to Result types.
|
|
227
|
+
*
|
|
228
|
+
* @param fn - Task function that returns a value or throws
|
|
229
|
+
* @param options - Optional configuration for this task
|
|
230
|
+
* @returns Promise that resolves with the task result wrapped in Result
|
|
231
|
+
*
|
|
232
|
+
* @example Basic Usage
|
|
233
|
+
* ```typescript
|
|
234
|
+
* const queue = createAsyncQueue<string>();
|
|
235
|
+
*
|
|
236
|
+
* queue.resultifyAdd(async () => {
|
|
237
|
+
* const response = await fetch('/api/data');
|
|
238
|
+
* return response.json();
|
|
239
|
+
* }).then(result => {
|
|
240
|
+
* if (result.ok) {
|
|
241
|
+
* console.log('Data:', result.value);
|
|
242
|
+
* } else {
|
|
243
|
+
* console.error('Failed:', result.error);
|
|
244
|
+
* }
|
|
245
|
+
* });
|
|
246
|
+
* ```
|
|
247
|
+
*
|
|
248
|
+
* @example With Callbacks
|
|
249
|
+
* ```typescript
|
|
250
|
+
* queue.resultifyAdd(
|
|
251
|
+
* async () => processData(),
|
|
252
|
+
* {
|
|
253
|
+
* onComplete: (data) => console.log('Processed:', data),
|
|
254
|
+
* onError: (error) => console.error('Failed:', error),
|
|
255
|
+
* timeout: 5000
|
|
256
|
+
* }
|
|
257
|
+
* );
|
|
258
|
+
* ```
|
|
259
|
+
*/
|
|
74
260
|
resultifyAdd(fn, options) {
|
|
75
261
|
return this.add(
|
|
76
262
|
(ctx) => resultify(async () => {
|
|
@@ -84,15 +270,31 @@ var AsyncQueue = class {
|
|
|
84
270
|
this.clear();
|
|
85
271
|
return;
|
|
86
272
|
}
|
|
273
|
+
if (this.#stopped || this.#paused || !this.#started) {
|
|
274
|
+
return;
|
|
275
|
+
}
|
|
87
276
|
if (this.#pending >= this.#concurrency || this.#queue.length === 0) {
|
|
88
277
|
return;
|
|
89
278
|
}
|
|
279
|
+
if (this.#isRateLimited()) {
|
|
280
|
+
const delay = this.#getRateLimitDelay();
|
|
281
|
+
if (delay > 0) {
|
|
282
|
+
const timeoutId = setTimeout(() => {
|
|
283
|
+
this.#rateLimitTimeouts.delete(timeoutId);
|
|
284
|
+
this.#processQueue();
|
|
285
|
+
}, delay);
|
|
286
|
+
this.#rateLimitTimeouts.add(timeoutId);
|
|
287
|
+
return;
|
|
288
|
+
}
|
|
289
|
+
}
|
|
90
290
|
const task = this.#queue.shift();
|
|
91
291
|
if (!task) {
|
|
92
292
|
return;
|
|
93
293
|
}
|
|
94
294
|
this.#pending++;
|
|
95
295
|
this.#size--;
|
|
296
|
+
this.#resolveSizeLessThanWaiters();
|
|
297
|
+
this.#recordTaskExecution();
|
|
96
298
|
const signals = [];
|
|
97
299
|
if (task.signal) {
|
|
98
300
|
signals.push(task.signal);
|
|
@@ -100,8 +302,8 @@ var AsyncQueue = class {
|
|
|
100
302
|
if (this.#signal) {
|
|
101
303
|
signals.push(this.#signal);
|
|
102
304
|
}
|
|
103
|
-
if (task.timeout) {
|
|
104
|
-
signals.push(
|
|
305
|
+
if (task.timeout !== void 0) {
|
|
306
|
+
signals.push(_AsyncQueue.#createTimeoutSignal(task.timeout));
|
|
105
307
|
}
|
|
106
308
|
const signal = signals.length > 1 ? AbortSignal.any(signals) : signals[0];
|
|
107
309
|
let abortListener;
|
|
@@ -112,10 +314,11 @@ var AsyncQueue = class {
|
|
|
112
314
|
}
|
|
113
315
|
const signalAbortPromise = new Promise((_, reject) => {
|
|
114
316
|
if (signal) {
|
|
115
|
-
const error = signal.reason instanceof Error ? signal.reason : new DOMException("This operation was aborted", "AbortError");
|
|
116
317
|
abortListener = () => {
|
|
318
|
+
const reason = signal.reason;
|
|
319
|
+
const err = reason instanceof Error ? reason : new DOMException("This operation was aborted", "AbortError");
|
|
117
320
|
setTimeout(() => {
|
|
118
|
-
reject(
|
|
321
|
+
reject(err);
|
|
119
322
|
}, 0);
|
|
120
323
|
};
|
|
121
324
|
signal.addEventListener("abort", abortListener, { once: true });
|
|
@@ -126,12 +329,13 @@ var AsyncQueue = class {
|
|
|
126
329
|
const result = await Promise.race([taskRunPromise, signalAbortPromise]);
|
|
127
330
|
if (isResult(result)) {
|
|
128
331
|
task.resolve(result);
|
|
129
|
-
if (result.
|
|
332
|
+
if (!result.ok) {
|
|
130
333
|
this.#failed++;
|
|
131
334
|
this.events.emit("error", {
|
|
132
335
|
meta: task.meta,
|
|
133
336
|
error: result.error
|
|
134
337
|
});
|
|
338
|
+
this.#stopOnErrorAndRejectPending(unknownToError(result.error));
|
|
135
339
|
} else {
|
|
136
340
|
this.#completed++;
|
|
137
341
|
this.events.emit("complete", {
|
|
@@ -147,21 +351,24 @@ var AsyncQueue = class {
|
|
|
147
351
|
meta: task.meta,
|
|
148
352
|
error
|
|
149
353
|
});
|
|
354
|
+
this.#stopOnErrorAndRejectPending(error);
|
|
150
355
|
}
|
|
151
356
|
} catch (error) {
|
|
152
|
-
|
|
357
|
+
const processedError = unknownToError(error);
|
|
358
|
+
task.resolve(Result.err(processedError));
|
|
153
359
|
this.#failed++;
|
|
154
360
|
this.events.emit("error", {
|
|
155
361
|
meta: task.meta,
|
|
156
|
-
error:
|
|
362
|
+
error: processedError
|
|
157
363
|
});
|
|
364
|
+
this.#stopOnErrorAndRejectPending(processedError);
|
|
158
365
|
} finally {
|
|
159
366
|
if (signal && abortListener) {
|
|
160
367
|
signal.removeEventListener("abort", abortListener);
|
|
161
368
|
}
|
|
162
369
|
this.#pending--;
|
|
163
370
|
this.#processQueue();
|
|
164
|
-
if (this.#pending === 0 && this.#size === 0) {
|
|
371
|
+
if (this.#pending === 0 && this.#size === 0 && this.#rateLimitTimeouts.size === 0) {
|
|
165
372
|
this.#resolveIdleWaiters();
|
|
166
373
|
}
|
|
167
374
|
}
|
|
@@ -174,33 +381,227 @@ var AsyncQueue = class {
|
|
|
174
381
|
}
|
|
175
382
|
}
|
|
176
383
|
}
|
|
384
|
+
#stopOnErrorAndRejectPending(error) {
|
|
385
|
+
if (!this.#stopOnError) {
|
|
386
|
+
return;
|
|
387
|
+
}
|
|
388
|
+
this.#stopped = true;
|
|
389
|
+
this.#stoppedReason = error;
|
|
390
|
+
if (this.#rejectPendingOnError) {
|
|
391
|
+
while (this.#queue.length > 0) {
|
|
392
|
+
const task = this.#queue.shift();
|
|
393
|
+
if (task) {
|
|
394
|
+
task.resolve(Result.err(error));
|
|
395
|
+
}
|
|
396
|
+
}
|
|
397
|
+
this.#size = 0;
|
|
398
|
+
this.#resolveSizeLessThanWaiters();
|
|
399
|
+
}
|
|
400
|
+
this.#resolveIdleWaiters();
|
|
401
|
+
}
|
|
402
|
+
/**
|
|
403
|
+
* Wait for the queue to become idle (no pending tasks, no queued tasks, and no rate-limit timers)
|
|
404
|
+
*
|
|
405
|
+
* This method resolves when:
|
|
406
|
+
* - All tasks have completed (success or failure)
|
|
407
|
+
* - The queue is stopped due to error (stopOnError), even with remaining tasks
|
|
408
|
+
* - There are no queued tasks, no running tasks, and no pending rate-limit timers
|
|
409
|
+
*
|
|
410
|
+
* @returns Promise that resolves when the queue is idle
|
|
411
|
+
*
|
|
412
|
+
* @example
|
|
413
|
+
* ```typescript
|
|
414
|
+
* const queue = createAsyncQueue<string>();
|
|
415
|
+
*
|
|
416
|
+
* // Add multiple tasks
|
|
417
|
+
* for (let i = 0; i < 10; i++) {
|
|
418
|
+
* queue.resultifyAdd(async () => `task ${i}`);
|
|
419
|
+
* }
|
|
420
|
+
*
|
|
421
|
+
* // Wait for all tasks to complete
|
|
422
|
+
* await queue.onIdle();
|
|
423
|
+
*
|
|
424
|
+
* console.log(`Completed: ${queue.completed}, Failed: ${queue.failed}`);
|
|
425
|
+
* ```
|
|
426
|
+
*/
|
|
177
427
|
async onIdle() {
|
|
178
|
-
if (this.#pending === 0 && this.#size === 0) {
|
|
428
|
+
if (this.#stopped || this.#pending === 0 && this.#size === 0 && this.#rateLimitTimeouts.size === 0) {
|
|
179
429
|
return Promise.resolve();
|
|
180
430
|
}
|
|
181
431
|
return new Promise((resolve) => {
|
|
182
432
|
this.#idleResolvers.push(resolve);
|
|
183
433
|
});
|
|
184
434
|
}
|
|
435
|
+
// removed: onEmpty()
|
|
436
|
+
/**
|
|
437
|
+
* Wait until the queued task count is below a limit
|
|
438
|
+
*
|
|
439
|
+
* Resolves immediately if `size < limit` at the moment of calling. This only
|
|
440
|
+
* considers queued (not yet started) tasks; running tasks are tracked by
|
|
441
|
+
* `pending`.
|
|
442
|
+
*
|
|
443
|
+
* @param limit Threshold that `size` must be below to resolve
|
|
444
|
+
*/
|
|
445
|
+
onSizeLessThan(limit) {
|
|
446
|
+
if (this.#size < limit) {
|
|
447
|
+
return Promise.resolve();
|
|
448
|
+
}
|
|
449
|
+
return new Promise((resolve) => {
|
|
450
|
+
this.#sizeLessThanWaiters.push({ limit, resolve });
|
|
451
|
+
});
|
|
452
|
+
}
|
|
453
|
+
/**
|
|
454
|
+
* Clear all queued tasks (does not affect currently running tasks)
|
|
455
|
+
*
|
|
456
|
+
* This removes all tasks waiting in the queue but allows currently
|
|
457
|
+
* executing tasks to complete normally.
|
|
458
|
+
*
|
|
459
|
+
* @example
|
|
460
|
+
* ```typescript
|
|
461
|
+
* const queue = createAsyncQueue({ concurrency: 1 });
|
|
462
|
+
*
|
|
463
|
+
* // Add multiple tasks
|
|
464
|
+
* queue.resultifyAdd(async () => longRunningTask()); // Will start immediately
|
|
465
|
+
* queue.resultifyAdd(async () => task2()); // Queued
|
|
466
|
+
* queue.resultifyAdd(async () => task3()); // Queued
|
|
467
|
+
*
|
|
468
|
+
* // Clear remaining queued tasks
|
|
469
|
+
* queue.clear();
|
|
470
|
+
*
|
|
471
|
+
* // Only the first task will complete
|
|
472
|
+
* await queue.onIdle();
|
|
473
|
+
* ```
|
|
474
|
+
*/
|
|
185
475
|
clear() {
|
|
186
476
|
this.#queue = [];
|
|
187
477
|
this.#size = 0;
|
|
478
|
+
for (const timeoutId of this.#rateLimitTimeouts) {
|
|
479
|
+
clearTimeout(timeoutId);
|
|
480
|
+
}
|
|
481
|
+
this.#rateLimitTimeouts.clear();
|
|
188
482
|
if (this.#pending === 0) {
|
|
189
483
|
this.#resolveIdleWaiters();
|
|
190
484
|
}
|
|
485
|
+
this.#resolveSizeLessThanWaiters();
|
|
191
486
|
}
|
|
487
|
+
/** Number of tasks that have completed successfully */
|
|
192
488
|
get completed() {
|
|
193
489
|
return this.#completed;
|
|
194
490
|
}
|
|
491
|
+
/** Number of tasks that have failed */
|
|
195
492
|
get failed() {
|
|
196
493
|
return this.#failed;
|
|
197
494
|
}
|
|
495
|
+
/** Number of tasks currently being processed */
|
|
198
496
|
get pending() {
|
|
199
497
|
return this.#pending;
|
|
200
498
|
}
|
|
499
|
+
/** Number of tasks waiting in the queue to be processed */
|
|
201
500
|
get size() {
|
|
202
501
|
return this.#size;
|
|
203
502
|
}
|
|
503
|
+
/**
|
|
504
|
+
* Manually start processing tasks (only needed if autoStart: false)
|
|
505
|
+
*
|
|
506
|
+
* @example
|
|
507
|
+
* ```typescript
|
|
508
|
+
* const queue = createAsyncQueue({ autoStart: false });
|
|
509
|
+
*
|
|
510
|
+
* // Add tasks without starting processing
|
|
511
|
+
* queue.resultifyAdd(async () => 'task1');
|
|
512
|
+
* queue.resultifyAdd(async () => 'task2');
|
|
513
|
+
*
|
|
514
|
+
* // Start processing when ready
|
|
515
|
+
* queue.start();
|
|
516
|
+
* await queue.onIdle();
|
|
517
|
+
* ```
|
|
518
|
+
*/
|
|
519
|
+
start() {
|
|
520
|
+
if (this.#stopped) {
|
|
521
|
+
return;
|
|
522
|
+
}
|
|
523
|
+
this.#started = true;
|
|
524
|
+
this.#processQueue();
|
|
525
|
+
}
|
|
526
|
+
/**
|
|
527
|
+
* Pause processing new tasks (currently running tasks continue)
|
|
528
|
+
*
|
|
529
|
+
* @example
|
|
530
|
+
* ```typescript
|
|
531
|
+
* const queue = createAsyncQueue();
|
|
532
|
+
*
|
|
533
|
+
* // Start some tasks
|
|
534
|
+
* queue.resultifyAdd(async () => longRunningTask1());
|
|
535
|
+
* queue.resultifyAdd(async () => longRunningTask2());
|
|
536
|
+
*
|
|
537
|
+
* // Pause before more tasks are picked up
|
|
538
|
+
* queue.pause();
|
|
539
|
+
*
|
|
540
|
+
* // Later, resume processing
|
|
541
|
+
* queue.resume();
|
|
542
|
+
* ```
|
|
543
|
+
*/
|
|
544
|
+
pause() {
|
|
545
|
+
this.#paused = true;
|
|
546
|
+
}
|
|
547
|
+
/**
|
|
548
|
+
* Resume processing tasks after pause
|
|
549
|
+
*/
|
|
550
|
+
resume() {
|
|
551
|
+
this.#paused = false;
|
|
552
|
+
if (this.#started && !this.#stopped) {
|
|
553
|
+
this.#processQueue();
|
|
554
|
+
}
|
|
555
|
+
}
|
|
556
|
+
/**
|
|
557
|
+
* Reset the queue after being stopped, allowing new tasks to be processed
|
|
558
|
+
*
|
|
559
|
+
* This clears the stopped state and error reason, and resumes processing
|
|
560
|
+
* any remaining queued tasks if autoStart was enabled.
|
|
561
|
+
*
|
|
562
|
+
* @example
|
|
563
|
+
* ```typescript
|
|
564
|
+
* const queue = createAsyncQueue({ stopOnError: true });
|
|
565
|
+
*
|
|
566
|
+
* // Add tasks that will cause the queue to stop
|
|
567
|
+
* queue.resultifyAdd(async () => { throw new Error('fail'); });
|
|
568
|
+
* queue.resultifyAdd(async () => 'remaining task');
|
|
569
|
+
*
|
|
570
|
+
* await queue.onIdle();
|
|
571
|
+
*
|
|
572
|
+
* if (queue.isStopped) {
|
|
573
|
+
* console.log(`Queue stopped, ${queue.size} tasks remaining`);
|
|
574
|
+
*
|
|
575
|
+
* // Reset and process remaining tasks
|
|
576
|
+
* queue.reset();
|
|
577
|
+
* await queue.onIdle();
|
|
578
|
+
* }
|
|
579
|
+
* ```
|
|
580
|
+
*/
|
|
581
|
+
reset() {
|
|
582
|
+
this.#stopped = false;
|
|
583
|
+
this.#stoppedReason = void 0;
|
|
584
|
+
if (this.#autoStart) {
|
|
585
|
+
this.#started = true;
|
|
586
|
+
this.#processQueue();
|
|
587
|
+
}
|
|
588
|
+
}
|
|
589
|
+
/** Whether the queue is stopped due to an error */
|
|
590
|
+
get isStopped() {
|
|
591
|
+
return this.#stopped;
|
|
592
|
+
}
|
|
593
|
+
/** Whether the queue is currently paused */
|
|
594
|
+
get isPaused() {
|
|
595
|
+
return this.#paused;
|
|
596
|
+
}
|
|
597
|
+
/** Whether the queue has been started (relevant for autoStart: false) */
|
|
598
|
+
get isStarted() {
|
|
599
|
+
return this.#started;
|
|
600
|
+
}
|
|
601
|
+
/** The error that caused the queue to stop (if any) */
|
|
602
|
+
get stoppedReason() {
|
|
603
|
+
return this.#stoppedReason;
|
|
604
|
+
}
|
|
204
605
|
};
|
|
205
606
|
var AsyncQueueWithMeta = class extends AsyncQueue {
|
|
206
607
|
constructor(options) {
|