@tanstack/pacer 0.5.0 → 0.6.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/cjs/async-debouncer.cjs +24 -13
- package/dist/cjs/async-debouncer.cjs.map +1 -1
- package/dist/cjs/async-debouncer.d.cts +49 -7
- package/dist/cjs/async-queuer.cjs +83 -114
- package/dist/cjs/async-queuer.cjs.map +1 -1
- package/dist/cjs/async-queuer.d.cts +79 -50
- package/dist/cjs/async-rate-limiter.cjs +20 -9
- package/dist/cjs/async-rate-limiter.cjs.map +1 -1
- package/dist/cjs/async-rate-limiter.d.cts +53 -3
- package/dist/cjs/async-throttler.cjs +24 -13
- package/dist/cjs/async-throttler.cjs.map +1 -1
- package/dist/cjs/async-throttler.d.cts +48 -6
- package/dist/cjs/types.d.cts +1 -0
- package/dist/esm/async-debouncer.d.ts +49 -7
- package/dist/esm/async-debouncer.js +24 -13
- package/dist/esm/async-debouncer.js.map +1 -1
- package/dist/esm/async-queuer.d.ts +79 -50
- package/dist/esm/async-queuer.js +83 -114
- package/dist/esm/async-queuer.js.map +1 -1
- package/dist/esm/async-rate-limiter.d.ts +53 -3
- package/dist/esm/async-rate-limiter.js +20 -9
- package/dist/esm/async-rate-limiter.js.map +1 -1
- package/dist/esm/async-throttler.d.ts +48 -6
- package/dist/esm/async-throttler.js +24 -13
- package/dist/esm/async-throttler.js.map +1 -1
- package/dist/esm/types.d.ts +1 -0
- package/package.json +1 -1
- package/src/async-debouncer.ts +66 -16
- package/src/async-queuer.ts +180 -169
- package/src/async-rate-limiter.ts +70 -12
- package/src/async-throttler.ts +66 -16
- package/src/types.ts +3 -0
package/src/async-queuer.ts
CHANGED
|
@@ -1,8 +1,10 @@
|
|
|
1
1
|
import { parseFunctionOrValue } from './utils'
|
|
2
|
-
import type { AnyAsyncFunction } from './types'
|
|
2
|
+
import type { AnyAsyncFunction, OptionalKeys } from './types'
|
|
3
3
|
import type { QueuePosition } from './queuer'
|
|
4
4
|
|
|
5
|
-
export
|
|
5
|
+
export type AsyncQueuerFn = AnyAsyncFunction & { priority?: number }
|
|
6
|
+
|
|
7
|
+
export interface AsyncQueuerOptions<TFn extends AsyncQueuerFn> {
|
|
6
8
|
/**
|
|
7
9
|
* Default position to add items to the queuer
|
|
8
10
|
* @default 'back'
|
|
@@ -13,7 +15,7 @@ export interface AsyncQueuerOptions<TValue> {
|
|
|
13
15
|
* Can be a number or a function that returns a number.
|
|
14
16
|
* @default 1
|
|
15
17
|
*/
|
|
16
|
-
concurrency?: number | ((queuer: AsyncQueuer<
|
|
18
|
+
concurrency?: number | ((queuer: AsyncQueuer<TFn>) => number)
|
|
17
19
|
/**
|
|
18
20
|
* Maximum time in milliseconds that an item can stay in the queue
|
|
19
21
|
* If not provided, items will never expire
|
|
@@ -23,7 +25,7 @@ export interface AsyncQueuerOptions<TValue> {
|
|
|
23
25
|
* Function to determine if an item has expired
|
|
24
26
|
* If provided, this overrides the expirationDuration behavior
|
|
25
27
|
*/
|
|
26
|
-
getIsExpired?: (item:
|
|
28
|
+
getIsExpired?: (item: TFn, addedAt: number) => boolean
|
|
27
29
|
/**
|
|
28
30
|
* Default position to get items from during processing
|
|
29
31
|
* @default 'front'
|
|
@@ -34,64 +36,89 @@ export interface AsyncQueuerOptions<TValue> {
|
|
|
34
36
|
* Higher priority items will be processed first
|
|
35
37
|
* If not provided, will use static priority values attached to tasks
|
|
36
38
|
*/
|
|
37
|
-
getPriority?: (item:
|
|
39
|
+
getPriority?: (item: TFn) => number
|
|
38
40
|
/**
|
|
39
41
|
* Initial items to populate the queuer with
|
|
40
42
|
*/
|
|
41
|
-
initialItems?: Array<
|
|
43
|
+
initialItems?: Array<TFn & { priority?: number }>
|
|
42
44
|
/**
|
|
43
45
|
* Maximum number of items allowed in the queuer
|
|
44
46
|
*/
|
|
45
47
|
maxSize?: number
|
|
48
|
+
/**
|
|
49
|
+
* Optional error handler for when a task throws.
|
|
50
|
+
* If provided, the handler will be called with the error and queuer instance.
|
|
51
|
+
* This can be used alongside throwOnError - the handler will be called before any error is thrown.
|
|
52
|
+
*/
|
|
53
|
+
onError?: (error: unknown, queuer: AsyncQueuer<TFn>) => void
|
|
54
|
+
/**
|
|
55
|
+
* Callback fired whenever an item expires in the queuer
|
|
56
|
+
*/
|
|
57
|
+
onExpire?: (item: TFn, queuer: AsyncQueuer<TFn>) => void
|
|
46
58
|
/**
|
|
47
59
|
* Callback fired whenever an item is removed from the queuer
|
|
48
60
|
*/
|
|
49
|
-
onGetNextItem?: (
|
|
50
|
-
item: () => Promise<TValue>,
|
|
51
|
-
queuer: AsyncQueuer<TValue>,
|
|
52
|
-
) => void
|
|
61
|
+
onGetNextItem?: (item: TFn, queuer: AsyncQueuer<TFn>) => void
|
|
53
62
|
/**
|
|
54
63
|
* Callback fired whenever the queuer's running state changes
|
|
55
64
|
*/
|
|
56
|
-
onIsRunningChange?: (queuer: AsyncQueuer<
|
|
65
|
+
onIsRunningChange?: (queuer: AsyncQueuer<TFn>) => void
|
|
57
66
|
/**
|
|
58
67
|
* Callback fired whenever an item is added or removed from the queuer
|
|
59
68
|
*/
|
|
60
|
-
onItemsChange?: (queuer: AsyncQueuer<
|
|
69
|
+
onItemsChange?: (queuer: AsyncQueuer<TFn>) => void
|
|
61
70
|
/**
|
|
62
71
|
* Callback fired whenever an item is rejected from being added to the queuer
|
|
63
72
|
*/
|
|
64
|
-
onReject?: (item:
|
|
73
|
+
onReject?: (item: TFn, queuer: AsyncQueuer<TFn>) => void
|
|
65
74
|
/**
|
|
66
|
-
*
|
|
75
|
+
* Optional callback to call when a task is settled
|
|
76
|
+
*/
|
|
77
|
+
onSettled?: (queuer: AsyncQueuer<TFn>) => void
|
|
78
|
+
/**
|
|
79
|
+
* Optional callback to call when a task succeeds
|
|
67
80
|
*/
|
|
68
|
-
|
|
81
|
+
onSuccess?: (result: TFn, queuer: AsyncQueuer<TFn>) => void
|
|
69
82
|
/**
|
|
70
83
|
* Whether the queuer should start processing tasks immediately or not.
|
|
71
84
|
*/
|
|
72
85
|
started?: boolean
|
|
86
|
+
/**
|
|
87
|
+
* Whether to throw errors when they occur.
|
|
88
|
+
* Defaults to true if no onError handler is provided, false if an onError handler is provided.
|
|
89
|
+
* Can be explicitly set to override these defaults.
|
|
90
|
+
*/
|
|
91
|
+
throwOnError?: boolean
|
|
73
92
|
/**
|
|
74
93
|
* Time in milliseconds to wait between processing items.
|
|
75
94
|
* Can be a number or a function that returns a number.
|
|
76
95
|
* @default 0
|
|
77
96
|
*/
|
|
78
|
-
wait?: number | ((queuer: AsyncQueuer<
|
|
97
|
+
wait?: number | ((queuer: AsyncQueuer<TFn>) => number)
|
|
79
98
|
}
|
|
80
99
|
|
|
81
|
-
|
|
100
|
+
type AsyncQueuerOptionsWithOptionalCallbacks = OptionalKeys<
|
|
101
|
+
Required<AsyncQueuerOptions<any>>,
|
|
102
|
+
| 'onError'
|
|
103
|
+
| 'onExpire'
|
|
104
|
+
| 'onGetNextItem'
|
|
105
|
+
| 'onIsRunningChange'
|
|
106
|
+
| 'onItemsChange'
|
|
107
|
+
| 'onReject'
|
|
108
|
+
| 'onSettled'
|
|
109
|
+
| 'onSuccess'
|
|
110
|
+
| 'throwOnError'
|
|
111
|
+
>
|
|
112
|
+
|
|
113
|
+
const defaultOptions: AsyncQueuerOptionsWithOptionalCallbacks = {
|
|
82
114
|
addItemsTo: 'back',
|
|
83
115
|
concurrency: 1,
|
|
84
116
|
expirationDuration: Infinity,
|
|
85
117
|
getIsExpired: () => false,
|
|
86
118
|
getItemsFrom: 'front',
|
|
87
|
-
getPriority: (item) =>
|
|
119
|
+
getPriority: (item: any) => item?.priority ?? 0,
|
|
88
120
|
initialItems: [],
|
|
89
121
|
maxSize: Infinity,
|
|
90
|
-
onGetNextItem: () => {},
|
|
91
|
-
onIsRunningChange: () => {},
|
|
92
|
-
onItemsChange: () => {},
|
|
93
|
-
onReject: () => {},
|
|
94
|
-
onExpire: () => {},
|
|
95
122
|
started: true,
|
|
96
123
|
wait: 0,
|
|
97
124
|
}
|
|
@@ -111,37 +138,48 @@ const defaultOptions: Required<AsyncQueuerOptions<any>> = {
|
|
|
111
138
|
* Tasks are processed concurrently up to the configured concurrency limit. When a task completes,
|
|
112
139
|
* the next pending task is processed if below the concurrency limit.
|
|
113
140
|
*
|
|
141
|
+
* Error Handling:
|
|
142
|
+
* - If an `onError` handler is provided, it will be called with the error and queuer instance
|
|
143
|
+
* - If `throwOnError` is true (default when no onError handler is provided), the error will be thrown
|
|
144
|
+
* - If `throwOnError` is false (default when onError handler is provided), the error will be swallowed
|
|
145
|
+
* - Both onError and throwOnError can be used together - the handler will be called before any error is thrown
|
|
146
|
+
* - The error state can be checked using the underlying AsyncQueuer instance
|
|
147
|
+
*
|
|
114
148
|
* @example
|
|
115
149
|
* ```ts
|
|
116
|
-
* const asyncQueuer = new AsyncQueuer<string>({
|
|
150
|
+
* const asyncQueuer = new AsyncQueuer<string>({
|
|
151
|
+
* concurrency: 2,
|
|
152
|
+
* onSuccess: (result) => {
|
|
153
|
+
* console.log(result); // 'Hello'
|
|
154
|
+
* }
|
|
155
|
+
* });
|
|
117
156
|
*
|
|
118
157
|
* asyncQueuer.addItem(async () => {
|
|
119
158
|
* return 'Hello';
|
|
120
159
|
* });
|
|
121
160
|
*
|
|
122
161
|
* asyncQueuer.start();
|
|
123
|
-
*
|
|
124
|
-
* asyncQueuer.onSuccess((result) => {
|
|
125
|
-
* console.log(result); // 'Hello'
|
|
126
|
-
* });
|
|
127
162
|
* ```
|
|
128
163
|
*/
|
|
129
|
-
export class AsyncQueuer<
|
|
130
|
-
private _options:
|
|
131
|
-
private _activeItems: Set<
|
|
132
|
-
private
|
|
164
|
+
export class AsyncQueuer<TFn extends AsyncQueuerFn> {
|
|
165
|
+
private _options: AsyncQueuerOptionsWithOptionalCallbacks
|
|
166
|
+
private _activeItems: Set<TFn> = new Set()
|
|
167
|
+
private _successCount = 0
|
|
168
|
+
private _errorCount = 0
|
|
169
|
+
private _settledCount = 0
|
|
133
170
|
private _rejectionCount = 0
|
|
134
171
|
private _expirationCount = 0
|
|
135
|
-
private _items: Array<
|
|
172
|
+
private _items: Array<TFn> = []
|
|
136
173
|
private _itemTimestamps: Array<number> = []
|
|
137
|
-
private _onErrorCallbacks: Array<(error: Error) => void> = []
|
|
138
|
-
private _onSettledCallbacks: Array<(result: TValue | Error) => void> = []
|
|
139
|
-
private _onSuccessCallbacks: Array<(result: TValue) => void> = []
|
|
140
174
|
private _pendingTick = false
|
|
141
175
|
private _running: boolean
|
|
142
176
|
|
|
143
|
-
constructor(initialOptions: AsyncQueuerOptions<
|
|
144
|
-
this._options = {
|
|
177
|
+
constructor(initialOptions: AsyncQueuerOptions<TFn> = defaultOptions) {
|
|
178
|
+
this._options = {
|
|
179
|
+
...defaultOptions,
|
|
180
|
+
...initialOptions,
|
|
181
|
+
throwOnError: initialOptions.throwOnError ?? !initialOptions.onError,
|
|
182
|
+
}
|
|
145
183
|
this._running = this._options.started
|
|
146
184
|
|
|
147
185
|
for (let i = 0; i < this._options.initialItems.length; i++) {
|
|
@@ -155,14 +193,14 @@ export class AsyncQueuer<TValue> {
|
|
|
155
193
|
* Updates the queuer options
|
|
156
194
|
* Returns the new options state
|
|
157
195
|
*/
|
|
158
|
-
setOptions(newOptions: Partial<AsyncQueuerOptions<
|
|
196
|
+
setOptions(newOptions: Partial<AsyncQueuerOptions<TFn>>): void {
|
|
159
197
|
this._options = { ...this._options, ...newOptions }
|
|
160
198
|
}
|
|
161
199
|
|
|
162
200
|
/**
|
|
163
201
|
* Returns the current queuer options
|
|
164
202
|
*/
|
|
165
|
-
getOptions():
|
|
203
|
+
getOptions(): AsyncQueuerOptions<TFn> {
|
|
166
204
|
return this._options
|
|
167
205
|
}
|
|
168
206
|
|
|
@@ -201,28 +239,28 @@ export class AsyncQueuer<TValue> {
|
|
|
201
239
|
break
|
|
202
240
|
}
|
|
203
241
|
this._activeItems.add(nextFn)
|
|
204
|
-
this._options.onItemsChange(this)
|
|
242
|
+
this._options.onItemsChange?.(this)
|
|
205
243
|
;(async () => {
|
|
206
|
-
let
|
|
207
|
-
let res!: TValue
|
|
208
|
-
let error: Error | undefined
|
|
244
|
+
let res!: TFn
|
|
209
245
|
|
|
210
246
|
try {
|
|
211
247
|
res = await nextFn()
|
|
212
|
-
|
|
213
|
-
|
|
214
|
-
|
|
248
|
+
this._successCount++
|
|
249
|
+
this._options.onSuccess?.(res, this)
|
|
250
|
+
} catch (error) {
|
|
251
|
+
this._errorCount++
|
|
252
|
+
this._options.onError?.(error, this)
|
|
253
|
+
if (this._options.throwOnError) {
|
|
254
|
+
throw error
|
|
255
|
+
} else {
|
|
256
|
+
console.error(error)
|
|
257
|
+
}
|
|
215
258
|
} finally {
|
|
259
|
+
this._settledCount++
|
|
216
260
|
this._activeItems.delete(nextFn)
|
|
217
|
-
this._options.onItemsChange(this)
|
|
218
|
-
|
|
219
|
-
|
|
220
|
-
if (success) {
|
|
221
|
-
this._onSuccessCallbacks.forEach((cb) => cb(res))
|
|
222
|
-
} else {
|
|
223
|
-
this._onErrorCallbacks.forEach((cb) => cb(error!))
|
|
261
|
+
this._options.onItemsChange?.(this)
|
|
262
|
+
this._options.onSettled?.(this)
|
|
224
263
|
}
|
|
225
|
-
this._onSettledCallbacks.forEach((cb) => cb(success ? res : error!))
|
|
226
264
|
|
|
227
265
|
const wait = this.getWait()
|
|
228
266
|
if (wait > 0) {
|
|
@@ -240,7 +278,7 @@ export class AsyncQueuer<TValue> {
|
|
|
240
278
|
/**
|
|
241
279
|
* Checks for and removes expired items from the queuer
|
|
242
280
|
*/
|
|
243
|
-
private checkExpiredItems() {
|
|
281
|
+
private checkExpiredItems(): void {
|
|
244
282
|
if (
|
|
245
283
|
this._options.expirationDuration === Infinity &&
|
|
246
284
|
this._options.getIsExpired === defaultOptions.getIsExpired
|
|
@@ -279,11 +317,11 @@ export class AsyncQueuer<TValue> {
|
|
|
279
317
|
this._items.splice(index, 1)
|
|
280
318
|
this._itemTimestamps.splice(index, 1)
|
|
281
319
|
this._expirationCount++
|
|
282
|
-
this._options.onExpire(expiredItem, this)
|
|
320
|
+
this._options.onExpire?.(expiredItem, this)
|
|
283
321
|
}
|
|
284
322
|
|
|
285
323
|
if (expiredIndices.length > 0) {
|
|
286
|
-
this._options.onItemsChange(this)
|
|
324
|
+
this._options.onItemsChange?.(this)
|
|
287
325
|
}
|
|
288
326
|
}
|
|
289
327
|
|
|
@@ -296,7 +334,7 @@ export class AsyncQueuer<TValue> {
|
|
|
296
334
|
this._pendingTick = true
|
|
297
335
|
this.tick()
|
|
298
336
|
}
|
|
299
|
-
this._options.onIsRunningChange(this)
|
|
337
|
+
this._options.onIsRunningChange?.(this)
|
|
300
338
|
|
|
301
339
|
return new Promise<void>((resolve) => {
|
|
302
340
|
const checkIdle = () => {
|
|
@@ -316,7 +354,7 @@ export class AsyncQueuer<TValue> {
|
|
|
316
354
|
stop(): void {
|
|
317
355
|
this._running = false
|
|
318
356
|
this._pendingTick = false
|
|
319
|
-
this._options.onIsRunningChange(this)
|
|
357
|
+
this._options.onIsRunningChange?.(this)
|
|
320
358
|
}
|
|
321
359
|
|
|
322
360
|
/**
|
|
@@ -324,7 +362,7 @@ export class AsyncQueuer<TValue> {
|
|
|
324
362
|
*/
|
|
325
363
|
clear(): void {
|
|
326
364
|
this._items = []
|
|
327
|
-
this._options.onItemsChange(this)
|
|
365
|
+
this._options.onItemsChange?.(this)
|
|
328
366
|
}
|
|
329
367
|
|
|
330
368
|
/**
|
|
@@ -332,7 +370,9 @@ export class AsyncQueuer<TValue> {
|
|
|
332
370
|
*/
|
|
333
371
|
reset(withInitialItems?: boolean): void {
|
|
334
372
|
this.clear()
|
|
335
|
-
this.
|
|
373
|
+
this._successCount = 0
|
|
374
|
+
this._errorCount = 0
|
|
375
|
+
this._settledCount = 0
|
|
336
376
|
if (withInitialItems) {
|
|
337
377
|
this._items = [...this._options.initialItems]
|
|
338
378
|
}
|
|
@@ -343,74 +383,59 @@ export class AsyncQueuer<TValue> {
|
|
|
343
383
|
* Adds a task to the queuer
|
|
344
384
|
*/
|
|
345
385
|
addItem(
|
|
346
|
-
fn:
|
|
386
|
+
fn: TFn,
|
|
347
387
|
position: QueuePosition = this._options.addItemsTo,
|
|
348
388
|
runOnItemsChange: boolean = true,
|
|
349
|
-
):
|
|
389
|
+
): void {
|
|
350
390
|
if (this.getIsFull()) {
|
|
351
391
|
this._rejectionCount++
|
|
352
|
-
this._options.onReject(fn, this)
|
|
353
|
-
return
|
|
392
|
+
this._options.onReject?.(fn, this)
|
|
393
|
+
return
|
|
354
394
|
}
|
|
355
395
|
|
|
356
|
-
|
|
357
|
-
|
|
358
|
-
|
|
359
|
-
|
|
360
|
-
|
|
361
|
-
|
|
362
|
-
|
|
363
|
-
|
|
364
|
-
|
|
365
|
-
|
|
366
|
-
|
|
367
|
-
|
|
368
|
-
|
|
369
|
-
|
|
370
|
-
|
|
371
|
-
|
|
372
|
-
|
|
373
|
-
this.
|
|
374
|
-
|
|
375
|
-
: task.priority
|
|
376
|
-
|
|
377
|
-
if (priority !== undefined) {
|
|
378
|
-
// Insert based on priority
|
|
379
|
-
const insertIndex = this._items.findIndex((existing) => {
|
|
380
|
-
const existingPriority =
|
|
381
|
-
this._options.getPriority !== defaultOptions.getPriority
|
|
382
|
-
? this._options.getPriority(existing)
|
|
383
|
-
: (existing as any).priority
|
|
384
|
-
return existingPriority > priority
|
|
385
|
-
})
|
|
386
|
-
|
|
387
|
-
if (insertIndex === -1) {
|
|
388
|
-
this._items.push(task)
|
|
389
|
-
this._itemTimestamps.push(Date.now())
|
|
390
|
-
} else {
|
|
391
|
-
this._items.splice(insertIndex, 0, task)
|
|
392
|
-
this._itemTimestamps.splice(insertIndex, 0, Date.now())
|
|
393
|
-
}
|
|
396
|
+
// Get priority either from the function or from getPriority option
|
|
397
|
+
const priority =
|
|
398
|
+
this._options.getPriority !== defaultOptions.getPriority
|
|
399
|
+
? this._options.getPriority(fn)
|
|
400
|
+
: fn.priority
|
|
401
|
+
|
|
402
|
+
if (priority !== undefined) {
|
|
403
|
+
// Insert based on priority
|
|
404
|
+
const insertIndex = this._items.findIndex((existing) => {
|
|
405
|
+
const existingPriority =
|
|
406
|
+
this._options.getPriority !== defaultOptions.getPriority
|
|
407
|
+
? this._options.getPriority(existing)
|
|
408
|
+
: (existing as any).priority
|
|
409
|
+
return existingPriority > priority
|
|
410
|
+
})
|
|
411
|
+
|
|
412
|
+
if (insertIndex === -1) {
|
|
413
|
+
this._items.push(fn)
|
|
414
|
+
this._itemTimestamps.push(Date.now())
|
|
394
415
|
} else {
|
|
416
|
+
this._items.splice(insertIndex, 0, fn)
|
|
417
|
+
this._itemTimestamps.splice(insertIndex, 0, Date.now())
|
|
418
|
+
}
|
|
419
|
+
} else {
|
|
420
|
+
if (position === 'front') {
|
|
395
421
|
// Default FIFO/LIFO behavior
|
|
396
|
-
|
|
397
|
-
|
|
398
|
-
|
|
399
|
-
|
|
400
|
-
|
|
401
|
-
|
|
402
|
-
}
|
|
422
|
+
this._items.unshift(fn)
|
|
423
|
+
this._itemTimestamps.unshift(Date.now())
|
|
424
|
+
} else {
|
|
425
|
+
// LIFO
|
|
426
|
+
this._items.push(fn)
|
|
427
|
+
this._itemTimestamps.push(Date.now())
|
|
403
428
|
}
|
|
429
|
+
}
|
|
404
430
|
|
|
405
|
-
|
|
406
|
-
|
|
407
|
-
|
|
431
|
+
if (runOnItemsChange) {
|
|
432
|
+
this._options.onItemsChange?.(this)
|
|
433
|
+
}
|
|
408
434
|
|
|
409
|
-
|
|
410
|
-
|
|
411
|
-
|
|
412
|
-
|
|
413
|
-
})
|
|
435
|
+
if (this._running && !this._pendingTick) {
|
|
436
|
+
this._pendingTick = true
|
|
437
|
+
this.tick()
|
|
438
|
+
}
|
|
414
439
|
}
|
|
415
440
|
|
|
416
441
|
/**
|
|
@@ -418,8 +443,8 @@ export class AsyncQueuer<TValue> {
|
|
|
418
443
|
*/
|
|
419
444
|
getNextItem(
|
|
420
445
|
position: QueuePosition = this._options.getItemsFrom,
|
|
421
|
-
):
|
|
422
|
-
let item:
|
|
446
|
+
): TFn | undefined {
|
|
447
|
+
let item: TFn | undefined
|
|
423
448
|
|
|
424
449
|
if (position === 'front') {
|
|
425
450
|
item = this._items.shift()
|
|
@@ -430,9 +455,8 @@ export class AsyncQueuer<TValue> {
|
|
|
430
455
|
}
|
|
431
456
|
|
|
432
457
|
if (item !== undefined) {
|
|
433
|
-
this.
|
|
434
|
-
this._options.
|
|
435
|
-
this._options.onGetNextItem(item, this)
|
|
458
|
+
this._options.onItemsChange?.(this)
|
|
459
|
+
this._options.onGetNextItem?.(item, this)
|
|
436
460
|
}
|
|
437
461
|
return item
|
|
438
462
|
}
|
|
@@ -440,9 +464,7 @@ export class AsyncQueuer<TValue> {
|
|
|
440
464
|
/**
|
|
441
465
|
* Returns an item without removing it
|
|
442
466
|
*/
|
|
443
|
-
getPeek(
|
|
444
|
-
position: QueuePosition = 'front',
|
|
445
|
-
): (() => Promise<TValue>) | undefined {
|
|
467
|
+
getPeek(position: QueuePosition = 'front'): TFn | undefined {
|
|
446
468
|
if (position === 'front') {
|
|
447
469
|
return this._items[0]
|
|
448
470
|
}
|
|
@@ -473,29 +495,43 @@ export class AsyncQueuer<TValue> {
|
|
|
473
495
|
/**
|
|
474
496
|
* Returns a copy of all items in the queuer
|
|
475
497
|
*/
|
|
476
|
-
getAllItems(): Array<
|
|
498
|
+
getAllItems(): Array<TFn> {
|
|
477
499
|
return [...this.getActiveItems(), ...this.getPendingItems()]
|
|
478
500
|
}
|
|
479
501
|
|
|
480
502
|
/**
|
|
481
503
|
* Returns the active items
|
|
482
504
|
*/
|
|
483
|
-
getActiveItems(): Array<
|
|
505
|
+
getActiveItems(): Array<TFn> {
|
|
484
506
|
return Array.from(this._activeItems)
|
|
485
507
|
}
|
|
486
508
|
|
|
487
509
|
/**
|
|
488
510
|
* Returns the pending items
|
|
489
511
|
*/
|
|
490
|
-
getPendingItems(): Array<
|
|
512
|
+
getPendingItems(): Array<TFn> {
|
|
491
513
|
return [...this._items]
|
|
492
514
|
}
|
|
493
515
|
|
|
494
516
|
/**
|
|
495
|
-
* Returns the number of items that have been
|
|
517
|
+
* Returns the number of items that have been successfully processed
|
|
518
|
+
*/
|
|
519
|
+
getSuccessCount(): number {
|
|
520
|
+
return this._successCount
|
|
521
|
+
}
|
|
522
|
+
|
|
523
|
+
/**
|
|
524
|
+
* Returns the number of items that have failed processing
|
|
525
|
+
*/
|
|
526
|
+
getErrorCount(): number {
|
|
527
|
+
return this._errorCount
|
|
528
|
+
}
|
|
529
|
+
|
|
530
|
+
/**
|
|
531
|
+
* Returns the number of items that have completed processing (success or error)
|
|
496
532
|
*/
|
|
497
|
-
|
|
498
|
-
return this.
|
|
533
|
+
getSettledCount(): number {
|
|
534
|
+
return this._settledCount
|
|
499
535
|
}
|
|
500
536
|
|
|
501
537
|
/**
|
|
@@ -519,40 +555,6 @@ export class AsyncQueuer<TValue> {
|
|
|
519
555
|
return this._running && this.getIsEmpty() && this._activeItems.size === 0
|
|
520
556
|
}
|
|
521
557
|
|
|
522
|
-
/**
|
|
523
|
-
* Adds a callback to be called when a task succeeds
|
|
524
|
-
*/
|
|
525
|
-
onSuccess(cb: (result: TValue) => void) {
|
|
526
|
-
this._onSuccessCallbacks.push(cb)
|
|
527
|
-
return () => {
|
|
528
|
-
this._onSuccessCallbacks = this._onSuccessCallbacks.filter(
|
|
529
|
-
(d) => d !== cb,
|
|
530
|
-
)
|
|
531
|
-
}
|
|
532
|
-
}
|
|
533
|
-
|
|
534
|
-
/**
|
|
535
|
-
* Adds a callback to be called when a task errors
|
|
536
|
-
*/
|
|
537
|
-
onError(cb: (error: Error) => void) {
|
|
538
|
-
this._onErrorCallbacks.push(cb)
|
|
539
|
-
return () => {
|
|
540
|
-
this._onErrorCallbacks = this._onErrorCallbacks.filter((d) => d !== cb)
|
|
541
|
-
}
|
|
542
|
-
}
|
|
543
|
-
|
|
544
|
-
/**
|
|
545
|
-
* Adds a callback to be called when a task is settled
|
|
546
|
-
*/
|
|
547
|
-
onSettled(cb: (result: TValue | Error) => void) {
|
|
548
|
-
this._onSettledCallbacks.push(cb)
|
|
549
|
-
return () => {
|
|
550
|
-
this._onSettledCallbacks = this._onSettledCallbacks.filter(
|
|
551
|
-
(d) => d !== cb,
|
|
552
|
-
)
|
|
553
|
-
}
|
|
554
|
-
}
|
|
555
|
-
|
|
556
558
|
/**
|
|
557
559
|
* Returns the number of items that have expired from the queuer
|
|
558
560
|
*/
|
|
@@ -565,6 +567,13 @@ export class AsyncQueuer<TValue> {
|
|
|
565
567
|
* Creates a new AsyncQueuer instance with the given options and returns a bound addItem function.
|
|
566
568
|
* The queuer is automatically started and ready to process items.
|
|
567
569
|
*
|
|
570
|
+
* Error Handling:
|
|
571
|
+
* - If an `onError` handler is provided, it will be called with the error and queuer instance
|
|
572
|
+
* - If `throwOnError` is true (default when no onError handler is provided), the error will be thrown
|
|
573
|
+
* - If `throwOnError` is false (default when onError handler is provided), the error will be swallowed
|
|
574
|
+
* - Both onError and throwOnError can be used together - the handler will be called before any error is thrown
|
|
575
|
+
* - The error state can be checked using the underlying AsyncQueuer instance
|
|
576
|
+
*
|
|
568
577
|
* @example
|
|
569
578
|
* ```ts
|
|
570
579
|
* const enqueue = asyncQueue<string>();
|
|
@@ -578,7 +587,9 @@ export class AsyncQueuer<TValue> {
|
|
|
578
587
|
* @param options - Configuration options for the AsyncQueuer
|
|
579
588
|
* @returns A bound addItem function that can be used to add tasks to the queuer
|
|
580
589
|
*/
|
|
581
|
-
export function asyncQueue<
|
|
582
|
-
|
|
590
|
+
export function asyncQueue<TFn extends AsyncQueuerFn>(
|
|
591
|
+
options: AsyncQueuerOptions<TFn>,
|
|
592
|
+
) {
|
|
593
|
+
const queuer = new AsyncQueuer<TFn>(options)
|
|
583
594
|
return queuer.addItem.bind(queuer)
|
|
584
595
|
}
|