@tanstack/pacer 0.7.0 → 0.9.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-batcher.cjs +163 -0
- package/dist/cjs/async-batcher.cjs.map +1 -0
- package/dist/cjs/async-batcher.d.cts +273 -0
- package/dist/cjs/async-debouncer.cjs +149 -162
- package/dist/cjs/async-debouncer.cjs.map +1 -1
- package/dist/cjs/async-debouncer.d.cts +76 -57
- package/dist/cjs/async-queuer.cjs +282 -343
- package/dist/cjs/async-queuer.cjs.map +1 -1
- package/dist/cjs/async-queuer.d.cts +123 -102
- package/dist/cjs/async-rate-limiter.cjs +128 -185
- package/dist/cjs/async-rate-limiter.cjs.map +1 -1
- package/dist/cjs/async-rate-limiter.d.cts +72 -61
- package/dist/cjs/async-throttler.cjs +168 -178
- package/dist/cjs/async-throttler.cjs.map +1 -1
- package/dist/cjs/async-throttler.d.cts +97 -69
- package/dist/cjs/batcher.cjs +110 -119
- package/dist/cjs/batcher.cjs.map +1 -1
- package/dist/cjs/batcher.d.cts +76 -51
- package/dist/cjs/debouncer.cjs +97 -85
- package/dist/cjs/debouncer.cjs.map +1 -1
- package/dist/cjs/debouncer.d.cts +54 -26
- package/dist/cjs/index.cjs +3 -6
- package/dist/cjs/index.cjs.map +1 -1
- package/dist/cjs/index.d.cts +1 -1
- package/dist/cjs/queuer.cjs +246 -294
- package/dist/cjs/queuer.cjs.map +1 -1
- package/dist/cjs/queuer.d.cts +105 -84
- package/dist/cjs/rate-limiter.cjs +97 -130
- package/dist/cjs/rate-limiter.cjs.map +1 -1
- package/dist/cjs/rate-limiter.d.cts +50 -37
- package/dist/cjs/throttler.cjs +107 -123
- package/dist/cjs/throttler.cjs.map +1 -1
- package/dist/cjs/throttler.d.cts +59 -35
- package/dist/cjs/utils.cjs +0 -13
- package/dist/cjs/utils.cjs.map +1 -1
- package/dist/cjs/utils.d.cts +0 -1
- package/dist/esm/async-batcher.d.ts +273 -0
- package/dist/esm/async-batcher.js +163 -0
- package/dist/esm/async-batcher.js.map +1 -0
- package/dist/esm/async-debouncer.d.ts +76 -57
- package/dist/esm/async-debouncer.js +149 -162
- package/dist/esm/async-debouncer.js.map +1 -1
- package/dist/esm/async-queuer.d.ts +123 -102
- package/dist/esm/async-queuer.js +282 -343
- package/dist/esm/async-queuer.js.map +1 -1
- package/dist/esm/async-rate-limiter.d.ts +72 -61
- package/dist/esm/async-rate-limiter.js +128 -185
- package/dist/esm/async-rate-limiter.js.map +1 -1
- package/dist/esm/async-throttler.d.ts +97 -69
- package/dist/esm/async-throttler.js +168 -178
- package/dist/esm/async-throttler.js.map +1 -1
- package/dist/esm/batcher.d.ts +76 -51
- package/dist/esm/batcher.js +110 -119
- package/dist/esm/batcher.js.map +1 -1
- package/dist/esm/debouncer.d.ts +54 -26
- package/dist/esm/debouncer.js +97 -85
- package/dist/esm/debouncer.js.map +1 -1
- package/dist/esm/index.d.ts +1 -1
- package/dist/esm/index.js +4 -7
- package/dist/esm/queuer.d.ts +105 -84
- package/dist/esm/queuer.js +246 -294
- package/dist/esm/queuer.js.map +1 -1
- package/dist/esm/rate-limiter.d.ts +50 -37
- package/dist/esm/rate-limiter.js +97 -130
- package/dist/esm/rate-limiter.js.map +1 -1
- package/dist/esm/throttler.d.ts +59 -35
- package/dist/esm/throttler.js +107 -123
- package/dist/esm/throttler.js.map +1 -1
- package/dist/esm/utils.d.ts +0 -1
- package/dist/esm/utils.js +0 -13
- package/dist/esm/utils.js.map +1 -1
- package/package.json +14 -11
- package/src/async-batcher.ts +475 -0
- package/src/async-debouncer.ts +201 -121
- package/src/async-queuer.ts +341 -220
- package/src/async-rate-limiter.ts +176 -136
- package/src/async-throttler.ts +233 -139
- package/src/batcher.ts +159 -93
- package/src/debouncer.ts +135 -52
- package/src/index.ts +1 -1
- package/src/queuer.ts +349 -227
- package/src/rate-limiter.ts +125 -80
- package/src/throttler.ts +152 -78
- package/src/utils.ts +0 -15
- package/dist/cjs/compare.cjs +0 -72
- package/dist/cjs/compare.cjs.map +0 -1
- package/dist/cjs/compare.d.cts +0 -12
- package/dist/esm/compare.d.ts +0 -12
- package/dist/esm/compare.js +0 -72
- package/dist/esm/compare.js.map +0 -1
- package/src/compare.ts +0 -105
package/src/async-throttler.ts
CHANGED
|
@@ -1,6 +1,67 @@
|
|
|
1
|
+
import { Store } from '@tanstack/store'
|
|
1
2
|
import { parseFunctionOrValue } from './utils'
|
|
2
3
|
import type { AnyAsyncFunction, OptionalKeys } from './types'
|
|
3
4
|
|
|
5
|
+
export interface AsyncThrottlerState<TFn extends AnyAsyncFunction> {
|
|
6
|
+
/**
|
|
7
|
+
* Number of function executions that have resulted in errors
|
|
8
|
+
*/
|
|
9
|
+
errorCount: number
|
|
10
|
+
/**
|
|
11
|
+
* Whether the throttled function is currently executing asynchronously
|
|
12
|
+
*/
|
|
13
|
+
isExecuting: boolean
|
|
14
|
+
/**
|
|
15
|
+
* Whether the throttler is waiting for the timeout to trigger execution
|
|
16
|
+
*/
|
|
17
|
+
isPending: boolean
|
|
18
|
+
/**
|
|
19
|
+
* The arguments from the most recent call to maybeExecute
|
|
20
|
+
*/
|
|
21
|
+
lastArgs: Parameters<TFn> | undefined
|
|
22
|
+
/**
|
|
23
|
+
* Timestamp of the last function execution in milliseconds
|
|
24
|
+
*/
|
|
25
|
+
lastExecutionTime: number
|
|
26
|
+
/**
|
|
27
|
+
* The result from the most recent successful function execution
|
|
28
|
+
*/
|
|
29
|
+
lastResult: ReturnType<TFn> | undefined
|
|
30
|
+
/**
|
|
31
|
+
* Timestamp when the next execution can occur in milliseconds
|
|
32
|
+
*/
|
|
33
|
+
nextExecutionTime: number
|
|
34
|
+
/**
|
|
35
|
+
* Number of function executions that have completed (either successfully or with errors)
|
|
36
|
+
*/
|
|
37
|
+
settleCount: number
|
|
38
|
+
/**
|
|
39
|
+
* Current execution status - 'idle' when not active, 'pending' when waiting, 'executing' when running, 'settled' when completed
|
|
40
|
+
*/
|
|
41
|
+
status: 'disabled' | 'idle' | 'pending' | 'executing' | 'settled'
|
|
42
|
+
/**
|
|
43
|
+
* Number of function executions that have completed successfully
|
|
44
|
+
*/
|
|
45
|
+
successCount: number
|
|
46
|
+
}
|
|
47
|
+
|
|
48
|
+
function getDefaultAsyncThrottlerState<
|
|
49
|
+
TFn extends AnyAsyncFunction,
|
|
50
|
+
>(): AsyncThrottlerState<TFn> {
|
|
51
|
+
return structuredClone({
|
|
52
|
+
errorCount: 0,
|
|
53
|
+
isExecuting: false,
|
|
54
|
+
isPending: false,
|
|
55
|
+
lastArgs: undefined,
|
|
56
|
+
lastExecutionTime: 0,
|
|
57
|
+
lastResult: undefined,
|
|
58
|
+
nextExecutionTime: 0,
|
|
59
|
+
settleCount: 0,
|
|
60
|
+
status: 'idle',
|
|
61
|
+
successCount: 0,
|
|
62
|
+
})
|
|
63
|
+
}
|
|
64
|
+
|
|
4
65
|
/**
|
|
5
66
|
* Options for configuring an async throttled function
|
|
6
67
|
*/
|
|
@@ -11,6 +72,10 @@ export interface AsyncThrottlerOptions<TFn extends AnyAsyncFunction> {
|
|
|
11
72
|
* Defaults to true.
|
|
12
73
|
*/
|
|
13
74
|
enabled?: boolean | ((throttler: AsyncThrottler<TFn>) => boolean)
|
|
75
|
+
/**
|
|
76
|
+
* Initial state for the async throttler
|
|
77
|
+
*/
|
|
78
|
+
initialState?: Partial<AsyncThrottlerState<TFn>>
|
|
14
79
|
/**
|
|
15
80
|
* Whether to execute the function immediately when called
|
|
16
81
|
* Defaults to true
|
|
@@ -54,7 +119,7 @@ export interface AsyncThrottlerOptions<TFn extends AnyAsyncFunction> {
|
|
|
54
119
|
|
|
55
120
|
type AsyncThrottlerOptionsWithOptionalCallbacks = OptionalKeys<
|
|
56
121
|
AsyncThrottlerOptions<any>,
|
|
57
|
-
'onError' | 'onSettled' | 'onSuccess'
|
|
122
|
+
'initialState' | 'onError' | 'onSettled' | 'onSuccess'
|
|
58
123
|
>
|
|
59
124
|
|
|
60
125
|
const defaultOptions: AsyncThrottlerOptionsWithOptionalCallbacks = {
|
|
@@ -85,6 +150,16 @@ const defaultOptions: AsyncThrottlerOptionsWithOptionalCallbacks = {
|
|
|
85
150
|
* - Both onError and throwOnError can be used together - the handler will be called before any error is thrown
|
|
86
151
|
* - The error state can be checked using the underlying AsyncThrottler instance
|
|
87
152
|
*
|
|
153
|
+
* State Management:
|
|
154
|
+
* - Uses TanStack Store for reactive state management
|
|
155
|
+
* - Use `initialState` to provide initial state values when creating the async throttler
|
|
156
|
+
* - Use `onSuccess` callback to react to successful function execution and implement custom logic
|
|
157
|
+
* - Use `onError` callback to react to function execution errors and implement custom error handling
|
|
158
|
+
* - Use `onSettled` callback to react to function execution completion (success or error) and implement custom logic
|
|
159
|
+
* - The state includes error count, execution status, last execution time, and success/settle counts
|
|
160
|
+
* - State can be accessed via `asyncThrottler.store.state` when using the class directly
|
|
161
|
+
* - When using framework adapters (React/Solid), state is accessed from `asyncThrottler.state`
|
|
162
|
+
*
|
|
88
163
|
* @example
|
|
89
164
|
* ```ts
|
|
90
165
|
* const throttler = new AsyncThrottler(async (value: string) => {
|
|
@@ -103,18 +178,13 @@ const defaultOptions: AsyncThrottlerOptionsWithOptionalCallbacks = {
|
|
|
103
178
|
* ```
|
|
104
179
|
*/
|
|
105
180
|
export class AsyncThrottler<TFn extends AnyAsyncFunction> {
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
private _nextExecutionTime = 0
|
|
114
|
-
private _settleCount = 0
|
|
115
|
-
private _successCount = 0
|
|
116
|
-
private _timeoutId: NodeJS.Timeout | null = null
|
|
117
|
-
private _resolvePreviousPromise:
|
|
181
|
+
readonly store: Store<Readonly<AsyncThrottlerState<TFn>>> = new Store<
|
|
182
|
+
AsyncThrottlerState<TFn>
|
|
183
|
+
>(getDefaultAsyncThrottlerState<TFn>())
|
|
184
|
+
options: AsyncThrottlerOptions<TFn>
|
|
185
|
+
#abortController: AbortController | null = null
|
|
186
|
+
#timeoutId: NodeJS.Timeout | null = null
|
|
187
|
+
#resolvePreviousPromise:
|
|
118
188
|
| ((value?: ReturnType<TFn> | undefined) => void)
|
|
119
189
|
| null = null
|
|
120
190
|
|
|
@@ -122,208 +192,222 @@ export class AsyncThrottler<TFn extends AnyAsyncFunction> {
|
|
|
122
192
|
private fn: TFn,
|
|
123
193
|
initialOptions: AsyncThrottlerOptions<TFn>,
|
|
124
194
|
) {
|
|
125
|
-
this.
|
|
195
|
+
this.options = {
|
|
126
196
|
...defaultOptions,
|
|
127
197
|
...initialOptions,
|
|
128
198
|
throwOnError: initialOptions.throwOnError ?? !initialOptions.onError,
|
|
129
199
|
}
|
|
200
|
+
this.#setState(this.options.initialState ?? {})
|
|
130
201
|
}
|
|
131
202
|
|
|
132
203
|
/**
|
|
133
|
-
* Updates the throttler options
|
|
204
|
+
* Updates the async throttler options
|
|
134
205
|
*/
|
|
135
|
-
setOptions(newOptions: Partial<AsyncThrottlerOptions<TFn>>): void {
|
|
136
|
-
this.
|
|
206
|
+
setOptions = (newOptions: Partial<AsyncThrottlerOptions<TFn>>): void => {
|
|
207
|
+
this.options = { ...this.options, ...newOptions }
|
|
137
208
|
|
|
138
|
-
// End the pending state if the
|
|
139
|
-
if (!this
|
|
209
|
+
// End the pending state if the throttler is disabled
|
|
210
|
+
if (!this.#getEnabled()) {
|
|
140
211
|
this.cancel()
|
|
141
212
|
}
|
|
142
213
|
}
|
|
143
214
|
|
|
144
|
-
|
|
145
|
-
|
|
146
|
-
|
|
147
|
-
|
|
148
|
-
|
|
215
|
+
#setState = (newState: Partial<AsyncThrottlerState<TFn>>): void => {
|
|
216
|
+
this.store.setState((state) => {
|
|
217
|
+
const combinedState = {
|
|
218
|
+
...state,
|
|
219
|
+
...newState,
|
|
220
|
+
}
|
|
221
|
+
const { isPending, isExecuting, settleCount } = combinedState
|
|
222
|
+
return {
|
|
223
|
+
...combinedState,
|
|
224
|
+
status: !this.#getEnabled()
|
|
225
|
+
? 'disabled'
|
|
226
|
+
: isPending
|
|
227
|
+
? 'pending'
|
|
228
|
+
: isExecuting
|
|
229
|
+
? 'executing'
|
|
230
|
+
: settleCount > 0
|
|
231
|
+
? 'settled'
|
|
232
|
+
: 'idle',
|
|
233
|
+
}
|
|
234
|
+
})
|
|
149
235
|
}
|
|
150
236
|
|
|
151
237
|
/**
|
|
152
|
-
* Returns the current enabled state of the throttler
|
|
238
|
+
* Returns the current enabled state of the async throttler
|
|
153
239
|
*/
|
|
154
|
-
getEnabled(): boolean {
|
|
155
|
-
return !!parseFunctionOrValue(this.
|
|
240
|
+
#getEnabled = (): boolean => {
|
|
241
|
+
return !!parseFunctionOrValue(this.options.enabled, this)
|
|
156
242
|
}
|
|
157
243
|
|
|
158
244
|
/**
|
|
159
245
|
* Returns the current wait time in milliseconds
|
|
160
246
|
*/
|
|
161
|
-
getWait(): number {
|
|
162
|
-
return parseFunctionOrValue(this.
|
|
247
|
+
#getWait = (): number => {
|
|
248
|
+
return parseFunctionOrValue(this.options.wait, this)
|
|
163
249
|
}
|
|
164
250
|
|
|
165
251
|
/**
|
|
166
|
-
* Attempts to execute the throttled function.
|
|
167
|
-
*
|
|
252
|
+
* Attempts to execute the throttled function. The execution behavior depends on the throttler options:
|
|
253
|
+
*
|
|
254
|
+
* - If enough time has passed since the last execution (>= wait period):
|
|
255
|
+
* - With leading=true: Executes immediately
|
|
256
|
+
* - With leading=false: Waits for the next trailing execution
|
|
257
|
+
*
|
|
258
|
+
* - If within the wait period:
|
|
259
|
+
* - With trailing=true: Schedules execution for end of wait period
|
|
260
|
+
* - With trailing=false: Drops the execution
|
|
168
261
|
*
|
|
169
|
-
*
|
|
170
|
-
*
|
|
171
|
-
*
|
|
172
|
-
* - If an `onError` handler is configured, errors will be caught and passed to the handler,
|
|
173
|
-
* and this method will return undefined.
|
|
174
|
-
* - The error state can be checked using `getErrorCount()` and `getIsExecuting()`.
|
|
262
|
+
* @example
|
|
263
|
+
* ```ts
|
|
264
|
+
* const throttled = new AsyncThrottler(fn, { wait: 1000 });
|
|
175
265
|
*
|
|
176
|
-
*
|
|
177
|
-
*
|
|
266
|
+
* // First call executes immediately
|
|
267
|
+
* await throttled.maybeExecute('a', 'b');
|
|
268
|
+
*
|
|
269
|
+
* // Call during wait period - gets throttled
|
|
270
|
+
* await throttled.maybeExecute('c', 'd');
|
|
271
|
+
* ```
|
|
178
272
|
*/
|
|
179
|
-
async
|
|
273
|
+
maybeExecute = async (
|
|
180
274
|
...args: Parameters<TFn>
|
|
181
|
-
): Promise<ReturnType<TFn> | undefined> {
|
|
275
|
+
): Promise<ReturnType<TFn> | undefined> => {
|
|
276
|
+
if (!this.#getEnabled()) return undefined
|
|
182
277
|
const now = Date.now()
|
|
183
|
-
const timeSinceLastExecution = now - this.
|
|
184
|
-
const wait = this
|
|
278
|
+
const timeSinceLastExecution = now - this.store.state.lastExecutionTime
|
|
279
|
+
const wait = this.#getWait()
|
|
280
|
+
// Store the most recent arguments for potential trailing execution
|
|
281
|
+
this.#setState({ lastArgs: args })
|
|
185
282
|
|
|
186
|
-
this
|
|
283
|
+
this.#resolvePreviousPromiseInternal()
|
|
187
284
|
|
|
188
285
|
// Handle leading execution
|
|
189
|
-
if (this.
|
|
190
|
-
await this
|
|
191
|
-
return this.
|
|
286
|
+
if (this.options.leading && timeSinceLastExecution >= wait) {
|
|
287
|
+
await this.#execute(...args)
|
|
288
|
+
return this.store.state.lastResult
|
|
192
289
|
} else {
|
|
193
|
-
// Store the most recent arguments for potential trailing execution
|
|
194
|
-
this._lastArgs = args
|
|
195
|
-
|
|
196
290
|
return new Promise((resolve) => {
|
|
197
|
-
this
|
|
291
|
+
this.#resolvePreviousPromise = resolve
|
|
198
292
|
// Clear any existing timeout to ensure we use the latest arguments
|
|
199
|
-
|
|
200
|
-
clearTimeout(this._timeoutId)
|
|
201
|
-
}
|
|
293
|
+
this.#clearTimeout()
|
|
202
294
|
|
|
203
295
|
// Set up trailing execution if enabled
|
|
204
|
-
if (this.
|
|
205
|
-
const _timeSinceLastExecution = this.
|
|
206
|
-
? now - this.
|
|
296
|
+
if (this.options.trailing) {
|
|
297
|
+
const _timeSinceLastExecution = this.store.state.lastExecutionTime
|
|
298
|
+
? now - this.store.state.lastExecutionTime
|
|
207
299
|
: 0
|
|
208
300
|
const timeoutDuration = wait - _timeSinceLastExecution
|
|
209
|
-
this
|
|
210
|
-
|
|
211
|
-
|
|
301
|
+
this.#setState({ isPending: true })
|
|
302
|
+
this.#timeoutId = setTimeout(async () => {
|
|
303
|
+
if (this.store.state.lastArgs !== undefined) {
|
|
304
|
+
await this.#execute(...this.store.state.lastArgs)
|
|
212
305
|
}
|
|
213
|
-
this
|
|
214
|
-
resolve(this.
|
|
306
|
+
this.#resolvePreviousPromise = null
|
|
307
|
+
resolve(this.store.state.lastResult)
|
|
215
308
|
}, timeoutDuration)
|
|
216
309
|
}
|
|
217
310
|
})
|
|
218
311
|
}
|
|
219
312
|
}
|
|
220
313
|
|
|
221
|
-
|
|
314
|
+
#execute = async (
|
|
222
315
|
...args: Parameters<TFn>
|
|
223
|
-
): Promise<ReturnType<TFn> | undefined> {
|
|
224
|
-
if (!this
|
|
225
|
-
this
|
|
316
|
+
): Promise<ReturnType<TFn> | undefined> => {
|
|
317
|
+
if (!this.#getEnabled() || this.store.state.isExecuting) return undefined
|
|
318
|
+
this.#abortController = new AbortController()
|
|
226
319
|
try {
|
|
227
|
-
this
|
|
228
|
-
|
|
229
|
-
this
|
|
230
|
-
|
|
320
|
+
this.#setState({ isExecuting: true })
|
|
321
|
+
const result = await this.fn(...args) // EXECUTE!
|
|
322
|
+
this.#setState({
|
|
323
|
+
lastResult: result,
|
|
324
|
+
successCount: this.store.state.successCount + 1,
|
|
325
|
+
})
|
|
326
|
+
this.options.onSuccess?.(result, this)
|
|
231
327
|
} catch (error) {
|
|
232
|
-
this
|
|
233
|
-
|
|
234
|
-
|
|
328
|
+
this.#setState({
|
|
329
|
+
errorCount: this.store.state.errorCount + 1,
|
|
330
|
+
})
|
|
331
|
+
this.options.onError?.(error, this)
|
|
332
|
+
if (this.options.throwOnError) {
|
|
235
333
|
throw error
|
|
236
334
|
} else {
|
|
237
335
|
console.error(error)
|
|
238
336
|
}
|
|
239
337
|
} finally {
|
|
240
|
-
|
|
241
|
-
this
|
|
242
|
-
this
|
|
243
|
-
|
|
244
|
-
|
|
245
|
-
|
|
246
|
-
|
|
247
|
-
|
|
248
|
-
|
|
249
|
-
|
|
250
|
-
|
|
251
|
-
if (this._resolvePreviousPromise) {
|
|
252
|
-
this._resolvePreviousPromise(this._lastResult)
|
|
253
|
-
this._resolvePreviousPromise = null
|
|
338
|
+
const lastExecutionTime = Date.now()
|
|
339
|
+
const nextExecutionTime = lastExecutionTime + this.#getWait()
|
|
340
|
+
this.#setState({
|
|
341
|
+
isExecuting: false,
|
|
342
|
+
isPending: false,
|
|
343
|
+
settleCount: this.store.state.settleCount + 1,
|
|
344
|
+
lastExecutionTime,
|
|
345
|
+
nextExecutionTime,
|
|
346
|
+
})
|
|
347
|
+
this.#abortController = null
|
|
348
|
+
this.options.onSettled?.(this)
|
|
254
349
|
}
|
|
350
|
+
return this.store.state.lastResult
|
|
255
351
|
}
|
|
256
352
|
|
|
257
353
|
/**
|
|
258
|
-
*
|
|
354
|
+
* Processes the current pending execution immediately
|
|
259
355
|
*/
|
|
260
|
-
|
|
261
|
-
if (this.
|
|
262
|
-
|
|
263
|
-
this
|
|
264
|
-
|
|
265
|
-
if (this._abortController) {
|
|
266
|
-
this._abortController.abort()
|
|
267
|
-
this._abortController = null
|
|
356
|
+
flush = (): void => {
|
|
357
|
+
if (this.store.state.isPending && this.store.state.lastArgs) {
|
|
358
|
+
this.#abortExecution() // abort any current execution
|
|
359
|
+
this.#clearTimeout() // clear any existing timeout
|
|
360
|
+
this.#execute(...this.store.state.lastArgs)
|
|
268
361
|
}
|
|
269
|
-
this.resolvePreviousPromise()
|
|
270
|
-
this._lastArgs = undefined
|
|
271
|
-
}
|
|
272
|
-
|
|
273
|
-
/**
|
|
274
|
-
* Returns the last execution time
|
|
275
|
-
*/
|
|
276
|
-
getLastExecutionTime(): number {
|
|
277
|
-
return this._lastExecutionTime
|
|
278
362
|
}
|
|
279
363
|
|
|
280
|
-
|
|
281
|
-
|
|
282
|
-
|
|
283
|
-
|
|
284
|
-
|
|
285
|
-
}
|
|
286
|
-
|
|
287
|
-
/**
|
|
288
|
-
* Returns the last result of the debounced function
|
|
289
|
-
*/
|
|
290
|
-
getLastResult(): ReturnType<TFn> | undefined {
|
|
291
|
-
return this._lastResult
|
|
364
|
+
#resolvePreviousPromiseInternal = (): void => {
|
|
365
|
+
if (this.#resolvePreviousPromise) {
|
|
366
|
+
this.#resolvePreviousPromise(this.store.state.lastResult)
|
|
367
|
+
this.#resolvePreviousPromise = null
|
|
368
|
+
}
|
|
292
369
|
}
|
|
293
370
|
|
|
294
|
-
|
|
295
|
-
|
|
296
|
-
|
|
297
|
-
|
|
298
|
-
|
|
371
|
+
#clearTimeout = (): void => {
|
|
372
|
+
if (this.#timeoutId) {
|
|
373
|
+
clearTimeout(this.#timeoutId)
|
|
374
|
+
this.#timeoutId = null
|
|
375
|
+
}
|
|
299
376
|
}
|
|
300
377
|
|
|
301
|
-
|
|
302
|
-
|
|
303
|
-
|
|
304
|
-
|
|
305
|
-
|
|
378
|
+
#cancelPendingExecution = (): void => {
|
|
379
|
+
this.#clearTimeout()
|
|
380
|
+
if (this.#resolvePreviousPromise) {
|
|
381
|
+
this.#resolvePreviousPromise(this.store.state.lastResult)
|
|
382
|
+
this.#resolvePreviousPromise = null
|
|
383
|
+
}
|
|
384
|
+
this.#setState({
|
|
385
|
+
isPending: false,
|
|
386
|
+
isExecuting: false,
|
|
387
|
+
lastArgs: undefined,
|
|
388
|
+
})
|
|
306
389
|
}
|
|
307
390
|
|
|
308
|
-
|
|
309
|
-
|
|
310
|
-
|
|
311
|
-
|
|
312
|
-
|
|
391
|
+
#abortExecution = (): void => {
|
|
392
|
+
if (this.#abortController) {
|
|
393
|
+
this.#abortController.abort()
|
|
394
|
+
this.#abortController = null
|
|
395
|
+
}
|
|
313
396
|
}
|
|
314
397
|
|
|
315
398
|
/**
|
|
316
|
-
*
|
|
399
|
+
* Cancels any pending execution or aborts any execution in progress
|
|
317
400
|
*/
|
|
318
|
-
|
|
319
|
-
|
|
401
|
+
cancel = (): void => {
|
|
402
|
+
this.#cancelPendingExecution()
|
|
403
|
+
this.#abortExecution()
|
|
320
404
|
}
|
|
321
405
|
|
|
322
406
|
/**
|
|
323
|
-
*
|
|
407
|
+
* Resets the debouncer state to its default values
|
|
324
408
|
*/
|
|
325
|
-
|
|
326
|
-
|
|
409
|
+
reset = (): void => {
|
|
410
|
+
this.#setState(getDefaultAsyncThrottlerState<TFn>())
|
|
327
411
|
}
|
|
328
412
|
}
|
|
329
413
|
|
|
@@ -343,6 +427,16 @@ export class AsyncThrottler<TFn extends AnyAsyncFunction> {
|
|
|
343
427
|
* - Both onError and throwOnError can be used together - the handler will be called before any error is thrown
|
|
344
428
|
* - The error state can be checked using the underlying AsyncThrottler instance
|
|
345
429
|
*
|
|
430
|
+
* State Management:
|
|
431
|
+
* - Uses TanStack Store for reactive state management
|
|
432
|
+
* - Use `initialState` to provide initial state values when creating the async throttler
|
|
433
|
+
* - Use `onSuccess` callback to react to successful function execution and implement custom logic
|
|
434
|
+
* - Use `onError` callback to react to function execution errors and implement custom error handling
|
|
435
|
+
* - Use `onSettled` callback to react to function execution completion (success or error) and implement custom logic
|
|
436
|
+
* - The state includes error count, execution status, last execution time, and success/settle counts
|
|
437
|
+
* - State can be accessed via the underlying AsyncThrottler instance's `store.state` property
|
|
438
|
+
* - When using framework adapters (React/Solid), state is accessed from the hook's state property
|
|
439
|
+
*
|
|
346
440
|
* @example
|
|
347
441
|
* ```ts
|
|
348
442
|
* const throttled = asyncThrottle(async (value: string) => {
|
|
@@ -365,5 +459,5 @@ export function asyncThrottle<TFn extends AnyAsyncFunction>(
|
|
|
365
459
|
initialOptions: AsyncThrottlerOptions<TFn>,
|
|
366
460
|
) {
|
|
367
461
|
const asyncThrottler = new AsyncThrottler(fn, initialOptions)
|
|
368
|
-
return asyncThrottler.maybeExecute
|
|
462
|
+
return asyncThrottler.maybeExecute
|
|
369
463
|
}
|