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