@tanstack/pacer 0.1.0 → 0.3.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 (74) hide show
  1. package/dist/cjs/async-debouncer.cjs +112 -63
  2. package/dist/cjs/async-debouncer.cjs.map +1 -1
  3. package/dist/cjs/async-debouncer.d.cts +66 -25
  4. package/dist/cjs/async-queuer.cjs +198 -124
  5. package/dist/cjs/async-queuer.cjs.map +1 -1
  6. package/dist/cjs/async-queuer.d.cts +91 -49
  7. package/dist/cjs/async-rate-limiter.cjs +83 -55
  8. package/dist/cjs/async-rate-limiter.cjs.map +1 -1
  9. package/dist/cjs/async-rate-limiter.d.cts +55 -28
  10. package/dist/cjs/async-throttler.cjs +121 -70
  11. package/dist/cjs/async-throttler.cjs.map +1 -1
  12. package/dist/cjs/async-throttler.d.cts +75 -25
  13. package/dist/cjs/debouncer.cjs +45 -23
  14. package/dist/cjs/debouncer.cjs.map +1 -1
  15. package/dist/cjs/debouncer.d.cts +27 -12
  16. package/dist/cjs/index.cjs +2 -0
  17. package/dist/cjs/index.cjs.map +1 -1
  18. package/dist/cjs/index.d.cts +2 -0
  19. package/dist/cjs/queuer.cjs +161 -101
  20. package/dist/cjs/queuer.cjs.map +1 -1
  21. package/dist/cjs/queuer.d.cts +80 -38
  22. package/dist/cjs/rate-limiter.cjs +52 -44
  23. package/dist/cjs/rate-limiter.cjs.map +1 -1
  24. package/dist/cjs/rate-limiter.d.cts +38 -46
  25. package/dist/cjs/throttler.cjs +57 -44
  26. package/dist/cjs/throttler.cjs.map +1 -1
  27. package/dist/cjs/throttler.d.cts +35 -23
  28. package/dist/cjs/types.d.cts +8 -0
  29. package/dist/cjs/utils.cjs +13 -0
  30. package/dist/cjs/utils.cjs.map +1 -0
  31. package/dist/cjs/utils.d.cts +1 -0
  32. package/dist/esm/async-debouncer.d.ts +66 -25
  33. package/dist/esm/async-debouncer.js +112 -63
  34. package/dist/esm/async-debouncer.js.map +1 -1
  35. package/dist/esm/async-queuer.d.ts +91 -49
  36. package/dist/esm/async-queuer.js +198 -124
  37. package/dist/esm/async-queuer.js.map +1 -1
  38. package/dist/esm/async-rate-limiter.d.ts +55 -28
  39. package/dist/esm/async-rate-limiter.js +83 -55
  40. package/dist/esm/async-rate-limiter.js.map +1 -1
  41. package/dist/esm/async-throttler.d.ts +75 -25
  42. package/dist/esm/async-throttler.js +121 -70
  43. package/dist/esm/async-throttler.js.map +1 -1
  44. package/dist/esm/debouncer.d.ts +27 -12
  45. package/dist/esm/debouncer.js +45 -23
  46. package/dist/esm/debouncer.js.map +1 -1
  47. package/dist/esm/index.d.ts +2 -0
  48. package/dist/esm/index.js +2 -0
  49. package/dist/esm/index.js.map +1 -1
  50. package/dist/esm/queuer.d.ts +80 -38
  51. package/dist/esm/queuer.js +161 -101
  52. package/dist/esm/queuer.js.map +1 -1
  53. package/dist/esm/rate-limiter.d.ts +38 -46
  54. package/dist/esm/rate-limiter.js +52 -44
  55. package/dist/esm/rate-limiter.js.map +1 -1
  56. package/dist/esm/throttler.d.ts +35 -23
  57. package/dist/esm/throttler.js +57 -44
  58. package/dist/esm/throttler.js.map +1 -1
  59. package/dist/esm/types.d.ts +8 -0
  60. package/dist/esm/utils.d.ts +1 -0
  61. package/dist/esm/utils.js +13 -0
  62. package/dist/esm/utils.js.map +1 -0
  63. package/package.json +8 -1
  64. package/src/async-debouncer.ts +157 -88
  65. package/src/async-queuer.ts +266 -148
  66. package/src/async-rate-limiter.ts +123 -83
  67. package/src/async-throttler.ts +173 -89
  68. package/src/debouncer.ts +71 -42
  69. package/src/index.ts +2 -0
  70. package/src/queuer.ts +219 -114
  71. package/src/rate-limiter.ts +74 -88
  72. package/src/throttler.ts +83 -65
  73. package/src/types.ts +9 -0
  74. package/src/utils.ts +13 -0
@@ -1,9 +1,9 @@
1
- import type { RateLimitRejectionInfo } from './rate-limiter'
1
+ import type { AnyAsyncFunction } from './types'
2
2
 
3
3
  /**
4
4
  * Options for configuring an async rate-limited function
5
5
  */
6
- export interface AsyncRateLimiterOptions {
6
+ export interface AsyncRateLimiterOptions<TFn extends AnyAsyncFunction> {
7
7
  /**
8
8
  * Whether the rate limiter is enabled. When disabled, maybeExecute will not trigger any executions.
9
9
  * Defaults to true.
@@ -14,17 +14,38 @@ export interface AsyncRateLimiterOptions {
14
14
  */
15
15
  limit: number
16
16
  /**
17
- * Time window in milliseconds within which the limit applies
17
+ * Optional error handler for when the rate-limited function throws
18
18
  */
19
- window: number
19
+ onError?: (error: unknown, rateLimiter: AsyncRateLimiter<TFn>) => void
20
+ /**
21
+ * Optional function to call when the rate-limited function is executed
22
+ */
23
+ onSettled?: (rateLimiter: AsyncRateLimiter<TFn>) => void
24
+ /**
25
+ * Optional function to call when the rate-limited function is executed
26
+ */
27
+ onSuccess?: (
28
+ result: ReturnType<TFn>,
29
+ rateLimiter: AsyncRateLimiter<TFn>,
30
+ ) => void
20
31
  /**
21
32
  * Optional callback function that is called when an execution is rejected due to rate limiting
22
33
  */
23
- onReject?: (info: RateLimitRejectionInfo) => void
34
+ onReject?: (rateLimiter: AsyncRateLimiter<TFn>) => void
24
35
  /**
25
- * Optional error handler for when the rate-limited function throws
36
+ * Time window in milliseconds within which the limit applies
26
37
  */
27
- onError?: (error: unknown) => void
38
+ window: number
39
+ }
40
+
41
+ const defaultOptions: Required<
42
+ Omit<AsyncRateLimiterOptions<any>, 'limit' | 'window'>
43
+ > = {
44
+ enabled: true,
45
+ onError: () => {},
46
+ onReject: () => {},
47
+ onSettled: () => {},
48
+ onSuccess: () => {},
28
49
  }
29
50
 
30
51
  /**
@@ -52,21 +73,21 @@ export interface AsyncRateLimiterOptions {
52
73
  * await rateLimiter.maybeExecute('123');
53
74
  * ```
54
75
  */
55
- export class AsyncRateLimiter<
56
- TFn extends (...args: Array<any>) => Promise<any>,
57
- TArgs extends Parameters<TFn>,
58
- > {
59
- private executionCount = 0
60
- private rejectionCount = 0
61
- private executionTimes: Array<number> = []
62
- private options: AsyncRateLimiterOptions
76
+ export class AsyncRateLimiter<TFn extends AnyAsyncFunction> {
77
+ private _options: AsyncRateLimiterOptions<TFn>
78
+ private _errorCount = 0
79
+ private _executionTimes: Array<number> = []
80
+ private _lastResult: ReturnType<TFn> | undefined
81
+ private _rejectionCount = 0
82
+ private _settleCount = 0
83
+ private _successCount = 0
63
84
 
64
85
  constructor(
65
86
  private fn: TFn,
66
- initialOptions: AsyncRateLimiterOptions,
87
+ initialOptions: AsyncRateLimiterOptions<TFn>,
67
88
  ) {
68
- this.options = {
69
- enabled: true,
89
+ this._options = {
90
+ ...defaultOptions,
70
91
  ...initialOptions,
71
92
  }
72
93
  }
@@ -75,36 +96,15 @@ export class AsyncRateLimiter<
75
96
  * Updates the rate limiter options
76
97
  * Returns the new options state
77
98
  */
78
- setOptions(
79
- newOptions: Partial<AsyncRateLimiterOptions>,
80
- ): AsyncRateLimiterOptions {
81
- this.options = {
82
- ...this.options,
83
- ...newOptions,
84
- }
85
- return this.options
99
+ setOptions(newOptions: Partial<AsyncRateLimiterOptions<TFn>>): void {
100
+ this._options = { ...this._options, ...newOptions }
86
101
  }
87
102
 
88
103
  /**
89
- * Returns the number of times the function has been executed
104
+ * Returns the current rate limiter options
90
105
  */
91
- getExecutionCount(): number {
92
- return this.executionCount
93
- }
94
-
95
- /**
96
- * Returns the number of times the function has been rejected
97
- */
98
- getRejectionCount(): number {
99
- return this.rejectionCount
100
- }
101
-
102
- /**
103
- * Returns the number of remaining executions allowed in the current window
104
- */
105
- getRemainingInWindow(): number {
106
- this.cleanupOldExecutions()
107
- return Math.max(0, this.options.limit - this.executionTimes.length)
106
+ getOptions(): Required<AsyncRateLimiterOptions<TFn>> {
107
+ return this._options as Required<AsyncRateLimiterOptions<TFn>>
108
108
  }
109
109
 
110
110
  /**
@@ -123,70 +123,109 @@ export class AsyncRateLimiter<
123
123
  * await rateLimiter.maybeExecute('arg1', 'arg2'); // Rejected
124
124
  * ```
125
125
  */
126
- async maybeExecute(...args: TArgs): Promise<boolean> {
126
+ async maybeExecute(
127
+ ...args: Parameters<TFn>
128
+ ): Promise<ReturnType<TFn> | undefined> {
127
129
  this.cleanupOldExecutions()
128
130
 
129
- if (this.executionTimes.length < this.options.limit) {
131
+ if (this._executionTimes.length < this._options.limit) {
130
132
  await this.executeFunction(...args)
131
- return true
133
+ return this._lastResult
132
134
  }
133
135
 
134
136
  this.rejectFunction()
135
- return false
137
+ return undefined
136
138
  }
137
139
 
138
- private async executeFunction(...args: TArgs): Promise<void> {
139
- if (!this.options.enabled) return
140
+ private async executeFunction(
141
+ ...args: Parameters<TFn>
142
+ ): Promise<ReturnType<TFn> | undefined> {
143
+ if (!this._options.enabled) return
140
144
  const now = Date.now()
141
- this.executionCount++
142
- this.executionTimes.push(now)
145
+ this._executionTimes.push(now)
143
146
 
144
147
  try {
145
- await this.fn(...args)
148
+ this._lastResult = await this.fn(...args)
149
+ this._successCount++
150
+ this._options.onSuccess?.(this._lastResult!, this)
146
151
  } catch (error) {
147
- if (this.options.onError) {
148
- try {
149
- this.options.onError(error)
150
- } catch {
151
- // Ignore errors from error handler
152
- }
153
- }
154
- throw error
152
+ this._errorCount++
153
+ this._options.onError?.(error, this)
155
154
  } finally {
155
+ this._settleCount++
156
+ this._options.onSettled?.(this)
156
157
  }
158
+
159
+ return this._lastResult
157
160
  }
158
161
 
159
162
  private rejectFunction(): void {
160
- this.rejectionCount++
161
- if (this.options.onReject) {
162
- const oldestExecution = Math.min(...this.executionTimes)
163
- const msUntilNextWindow =
164
- oldestExecution + this.options.window - Date.now()
165
-
166
- this.options.onReject({
167
- msUntilNextWindow,
168
- currentExecutions: this.executionTimes.length,
169
- limit: this.options.limit,
170
- rejectionCount: this.rejectionCount,
171
- })
163
+ this._rejectionCount++
164
+ if (this._options.onReject) {
165
+ this._options.onReject(this)
172
166
  }
173
167
  }
174
168
 
175
169
  private cleanupOldExecutions(): void {
176
170
  const now = Date.now()
177
- const windowStart = now - this.options.window
178
- this.executionTimes = this.executionTimes.filter(
171
+ const windowStart = now - this._options.window
172
+ this._executionTimes = this._executionTimes.filter(
179
173
  (time) => time > windowStart,
180
174
  )
181
175
  }
182
176
 
177
+ /**
178
+ * Returns the number of remaining executions allowed in the current window
179
+ */
180
+ getRemainingInWindow(): number {
181
+ this.cleanupOldExecutions()
182
+ return Math.max(0, this._options.limit - this._executionTimes.length)
183
+ }
184
+
185
+ /**
186
+ * Returns the number of milliseconds until the next execution will be possible
187
+ */
188
+ getMsUntilNextWindow(): number {
189
+ return this.getRemainingInWindow() * this._options.window
190
+ }
191
+
192
+ /**
193
+ * Returns the number of times the function has been executed
194
+ */
195
+ getSuccessCount(): number {
196
+ return this._successCount
197
+ }
198
+
199
+ /**
200
+ * Returns the number of times the function has been settled
201
+ */
202
+ getSettleCount(): number {
203
+ return this._settleCount
204
+ }
205
+
206
+ /**
207
+ * Returns the number of times the function has errored
208
+ */
209
+ getErrorCount(): number {
210
+ return this._errorCount
211
+ }
212
+
213
+ /**
214
+ * Returns the number of times the function has been rejected
215
+ */
216
+ getRejectionCount(): number {
217
+ return this._rejectionCount
218
+ }
219
+
183
220
  /**
184
221
  * Resets the rate limiter state
185
222
  */
186
223
  reset(): void {
187
- this.executionTimes = []
188
- this.executionCount = 0
189
- this.rejectionCount = 0
224
+ this._executionTimes = []
225
+ this._successCount = 0
226
+ this._errorCount = 0
227
+ this._rejectionCount = 0
228
+ this._settleCount = 0
190
229
  }
191
230
  }
192
231
 
@@ -207,8 +246,8 @@ export class AsyncRateLimiter<
207
246
  * const rateLimited = asyncRateLimit(makeApiCall, {
208
247
  * limit: 5,
209
248
  * window: 60000,
210
- * onReject: ({ msUntilNextWindow }) => {
211
- * console.log(`Rate limit exceeded. Try again in ${msUntilNextWindow}ms`);
249
+ * onReject: (rateLimiter) => {
250
+ * console.log(`Rate limit exceeded. Try again in ${rateLimiter.getMsUntilNextWindow()}ms`);
212
251
  * }
213
252
  * });
214
253
  *
@@ -220,9 +259,10 @@ export class AsyncRateLimiter<
220
259
  * const throttled = throttle(makeApiCall, { wait: 12000 }); // One call every 12 seconds
221
260
  * ```
222
261
  */
223
- export function asyncRateLimit<
224
- TFn extends (...args: Array<any>) => Promise<any>,
225
- >(fn: TFn, initialOptions: Omit<AsyncRateLimiterOptions, 'enabled'>) {
262
+ export function asyncRateLimit<TFn extends AnyAsyncFunction>(
263
+ fn: TFn,
264
+ initialOptions: Omit<AsyncRateLimiterOptions<TFn>, 'enabled'>,
265
+ ) {
226
266
  const rateLimiter = new AsyncRateLimiter(fn, initialOptions)
227
267
  return rateLimiter.maybeExecute.bind(rateLimiter)
228
268
  }
@@ -1,26 +1,53 @@
1
+ import type { AnyAsyncFunction } from './types'
2
+
1
3
  /**
2
4
  * Options for configuring an async throttled function
3
5
  */
4
- export interface AsyncThrottlerOptions {
6
+ export interface AsyncThrottlerOptions<TFn extends AnyAsyncFunction> {
5
7
  /**
6
8
  * Whether the throttler is enabled. When disabled, maybeExecute will not trigger any executions.
7
9
  * Defaults to true.
8
10
  */
9
11
  enabled?: boolean
10
12
  /**
11
- * Time window in milliseconds during which the function can only be executed once
12
- * Defaults to 0ms
13
+ * Whether to execute the function immediately when called
14
+ * Defaults to true
13
15
  */
14
- wait: number
16
+ leading?: boolean
15
17
  /**
16
18
  * Optional error handler for when the throttled function throws
17
19
  */
18
- onError?: (error: unknown) => void
20
+ onError?: (error: unknown, asyncThrottler: AsyncThrottler<TFn>) => void
21
+ /**
22
+ * Optional function to call when the throttled function is executed
23
+ */
24
+ onSettled?: (asyncThrottler: AsyncThrottler<TFn>) => void
25
+ /**
26
+ * Optional function to call when the throttled function is executed
27
+ */
28
+ onSuccess?: (
29
+ result: ReturnType<TFn>,
30
+ asyncThrottler: AsyncThrottler<TFn>,
31
+ ) => void
32
+ /**
33
+ * Whether to execute the function on the trailing edge of the wait period
34
+ * Defaults to true
35
+ */
36
+ trailing?: boolean
37
+ /**
38
+ * Time window in milliseconds during which the function can only be executed once
39
+ * Defaults to 0ms
40
+ */
41
+ wait: number
19
42
  }
20
43
 
21
- const defaultOptions: Required<AsyncThrottlerOptions> = {
44
+ const defaultOptions: Required<AsyncThrottlerOptions<any>> = {
22
45
  enabled: true,
46
+ leading: true,
23
47
  onError: () => {},
48
+ onSettled: () => {},
49
+ onSuccess: () => {},
50
+ trailing: true,
24
51
  wait: 0,
25
52
  }
26
53
 
@@ -46,23 +73,24 @@ const defaultOptions: Required<AsyncThrottlerOptions> = {
46
73
  * });
47
74
  * ```
48
75
  */
49
- export class AsyncThrottler<
50
- TFn extends (...args: Array<any>) => Promise<any>,
51
- TArgs extends Parameters<TFn>,
52
- > {
53
- private abortController: AbortController | null = null
54
- private executionCount = 0
55
- private isExecuting = false
56
- private isScheduled = false
57
- private lastArgs: TArgs | undefined
58
- private nextExecutionTime = 0
59
- private options: Required<AsyncThrottlerOptions>
76
+ export class AsyncThrottler<TFn extends AnyAsyncFunction> {
77
+ private _options: Required<AsyncThrottlerOptions<TFn>>
78
+ private _abortController: AbortController | null = null
79
+ private _errorCount = 0
80
+ private _isExecuting = false
81
+ private _lastArgs: Parameters<TFn> | undefined
82
+ private _lastExecutionTime = 0
83
+ private _lastResult: ReturnType<TFn> | undefined
84
+ private _nextExecutionTime = 0
85
+ private _settleCount = 0
86
+ private _successCount = 0
87
+ private _timeoutId: NodeJS.Timeout | null = null
60
88
 
61
89
  constructor(
62
90
  private fn: TFn,
63
- initialOptions: AsyncThrottlerOptions,
91
+ initialOptions: AsyncThrottlerOptions<TFn>,
64
92
  ) {
65
- this.options = {
93
+ this._options = {
66
94
  ...defaultOptions,
67
95
  ...initialOptions,
68
96
  }
@@ -72,101 +100,156 @@ export class AsyncThrottler<
72
100
  * Updates the throttler options
73
101
  * Returns the new options state
74
102
  */
75
- setOptions(
76
- newOptions: Partial<AsyncThrottlerOptions>,
77
- ): Required<AsyncThrottlerOptions> {
78
- this.options = {
79
- ...this.options,
80
- ...newOptions,
103
+ setOptions(newOptions: Partial<AsyncThrottlerOptions<TFn>>): void {
104
+ this._options = { ...this._options, ...newOptions }
105
+
106
+ // End the pending state if the debouncer is disabled
107
+ if (!this._options.enabled) {
108
+ this.cancel()
81
109
  }
82
- return this.options
83
110
  }
84
111
 
85
112
  /**
86
- * Returns the number of times the function has been executed
113
+ * Returns the current options
87
114
  */
88
- getExecutionCount(): number {
89
- return this.executionCount
115
+ getOptions(): Required<AsyncThrottlerOptions<TFn>> {
116
+ return this._options
90
117
  }
91
118
 
92
119
  /**
93
- * Returns the next execution time
120
+ * Attempts to execute the throttled function
121
+ * If a call is already in progress, it may be blocked or queued depending on the `wait` option
94
122
  */
95
- getNextExecutionTime(): number {
96
- return this.nextExecutionTime
123
+ async maybeExecute(
124
+ ...args: Parameters<TFn>
125
+ ): Promise<ReturnType<TFn> | undefined> {
126
+ const now = Date.now()
127
+ const timeSinceLastExecution = now - this._lastExecutionTime
128
+
129
+ // Handle leading execution
130
+ if (this._options.leading && timeSinceLastExecution >= this._options.wait) {
131
+ await this.executeFunction(...args)
132
+ return this._lastResult
133
+ } else {
134
+ // Store the most recent arguments for potential trailing execution
135
+ this._lastArgs = args
136
+
137
+ return new Promise((resolve) => {
138
+ // Clear any existing timeout to ensure we use the latest arguments
139
+ if (this._timeoutId) {
140
+ clearTimeout(this._timeoutId)
141
+ }
142
+
143
+ // Set up trailing execution if enabled
144
+ if (this._options.trailing) {
145
+ const _timeSinceLastExecution = this._lastExecutionTime
146
+ ? now - this._lastExecutionTime
147
+ : 0
148
+ const timeoutDuration = this._options.wait - _timeSinceLastExecution
149
+ this._timeoutId = setTimeout(async () => {
150
+ if (this._lastArgs !== undefined) {
151
+ await this.executeFunction(...this._lastArgs)
152
+ }
153
+ resolve(this._lastResult)
154
+ }, timeoutDuration)
155
+ }
156
+ })
157
+ }
158
+ }
159
+
160
+ private async executeFunction(
161
+ ...args: Parameters<TFn>
162
+ ): Promise<ReturnType<TFn> | undefined> {
163
+ if (!this._options.enabled || this._isExecuting) return undefined
164
+ this._abortController = new AbortController()
165
+ try {
166
+ this._isExecuting = true
167
+ this._lastResult = await this.fn(...args) // EXECUTE!
168
+ this._successCount++
169
+ this._options.onSuccess(this._lastResult!, this)
170
+ } catch (error) {
171
+ this._errorCount++
172
+ this._options.onError(error, this)
173
+ } finally {
174
+ this._isExecuting = false
175
+ this._settleCount++
176
+ this._abortController = null
177
+ this._lastExecutionTime = Date.now()
178
+ this._nextExecutionTime = this._lastExecutionTime + this._options.wait
179
+ this._options.onSettled(this)
180
+ }
181
+ return this._lastResult
97
182
  }
98
183
 
99
184
  /**
100
- * Cancels any pending execution
185
+ * Cancels any pending execution or aborts any execution in progress
101
186
  */
102
187
  cancel(): void {
103
- if (this.abortController) {
104
- this.abortController.abort()
105
- this.abortController = null
188
+ if (this._timeoutId) {
189
+ clearTimeout(this._timeoutId)
190
+ this._timeoutId = null
191
+ }
192
+ if (this._abortController) {
193
+ this._abortController.abort()
194
+ this._abortController = null
106
195
  }
107
- this.isScheduled = false
108
- this.lastArgs = undefined
196
+ this._lastArgs = undefined
109
197
  }
110
198
 
111
199
  /**
112
- * Attempts to execute the throttled function
113
- * If a call is already in progress, it may be blocked or queued depending on the `wait` option
200
+ * Returns the last execution time
114
201
  */
115
- async maybeExecute(...args: TArgs): Promise<void> {
116
- this.lastArgs = args
117
- if (this.isScheduled) return
118
- this.isScheduled = true
202
+ getLastExecutionTime(): number {
203
+ return this._lastExecutionTime
204
+ }
119
205
 
120
- this.abortController = new AbortController()
121
- const signal = this.abortController.signal
206
+ /**
207
+ * Returns the next execution time
208
+ */
209
+ getNextExecutionTime(): number {
210
+ return this._nextExecutionTime
211
+ }
122
212
 
123
- try {
124
- while (this.isExecuting) {
125
- await this.delay(this.options.wait, signal)
126
- }
213
+ /**
214
+ * Returns the last result of the debounced function
215
+ */
216
+ getLastResult(): ReturnType<TFn> | undefined {
217
+ return this._lastResult
218
+ }
127
219
 
128
- while (Date.now() < this.nextExecutionTime) {
129
- await this.delay(this.nextExecutionTime - Date.now(), signal)
130
- }
220
+ /**
221
+ * Returns the number of times the function has been executed successfully
222
+ */
223
+ getSuccessCount(): number {
224
+ return this._successCount
225
+ }
131
226
 
132
- this.isScheduled = false
133
- this.isExecuting = true
227
+ /**
228
+ * Returns the number of times the function has settled (completed or errored)
229
+ */
230
+ getSettleCount(): number {
231
+ return this._settleCount
232
+ }
134
233
 
135
- await this.executeFunction(...this.lastArgs)
136
- } catch (error) {
137
- if (error instanceof Error && error.name === 'AbortError') {
138
- return // Silent return on cancellation
139
- }
140
- try {
141
- this.options.onError(error)
142
- } catch {
143
- // Ignore errors from error handler
144
- }
145
- } finally {
146
- this.nextExecutionTime = Date.now() + this.options.wait
147
- this.isExecuting = false
148
- this.abortController = null
149
- }
234
+ /**
235
+ * Returns the number of times the function has errored
236
+ */
237
+ getErrorCount(): number {
238
+ return this._errorCount
150
239
  }
151
240
 
152
- private delay(ms: number, signal: AbortSignal): Promise<void> {
153
- return new Promise((resolve, reject) => {
154
- const timeout = setTimeout(resolve, ms)
155
- signal.addEventListener(
156
- 'abort',
157
- () => {
158
- clearTimeout(timeout)
159
- reject(new Error('AbortError'))
160
- },
161
- { once: true },
162
- )
163
- })
241
+ /**
242
+ * Returns the current pending state
243
+ */
244
+ getIsPending(): boolean {
245
+ return this._options.enabled && !!this._timeoutId
164
246
  }
165
247
 
166
- private async executeFunction(...args: TArgs): Promise<void> {
167
- if (!this.options.enabled) return
168
- this.executionCount++
169
- await this.fn(...args)
248
+ /**
249
+ * Returns the current executing state
250
+ */
251
+ getIsExecuting(): boolean {
252
+ return this._isExecuting
170
253
  }
171
254
  }
172
255
 
@@ -186,9 +269,10 @@ export class AsyncThrottler<
186
269
  * await throttled(); // Waits 1 second before executing
187
270
  * ```
188
271
  */
189
- export function asyncThrottle<
190
- TFn extends (...args: Array<any>) => Promise<any>,
191
- >(fn: TFn, initialOptions: Omit<AsyncThrottlerOptions, 'enabled'>) {
272
+ export function asyncThrottle<TFn extends AnyAsyncFunction>(
273
+ fn: TFn,
274
+ initialOptions: Omit<AsyncThrottlerOptions<TFn>, 'enabled'>,
275
+ ) {
192
276
  const asyncThrottler = new AsyncThrottler(fn, initialOptions)
193
277
  return asyncThrottler.maybeExecute.bind(asyncThrottler)
194
278
  }