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