@tanstack/pacer 0.2.0 → 0.4.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 +60 -24
- 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 +75 -38
- package/dist/cjs/async-rate-limiter.cjs.map +1 -1
- package/dist/cjs/async-rate-limiter.d.cts +76 -24
- package/dist/cjs/async-throttler.cjs +85 -57
- package/dist/cjs/async-throttler.cjs.map +1 -1
- package/dist/cjs/async-throttler.d.cts +66 -25
- 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 +19 -9
- package/dist/cjs/rate-limiter.cjs.map +1 -1
- package/dist/cjs/rate-limiter.d.cts +31 -11
- 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 +60 -24
- 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 +76 -24
- package/dist/esm/async-rate-limiter.js +75 -38
- package/dist/esm/async-rate-limiter.js.map +1 -1
- package/dist/esm/async-throttler.d.ts +66 -25
- 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 +31 -11
- package/dist/esm/rate-limiter.js +19 -9
- 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 +130 -81
- package/src/async-queuer.ts +93 -8
- package/src/async-rate-limiter.ts +141 -67
- package/src/async-throttler.ts +152 -94
- package/src/debouncer.ts +26 -33
- package/src/queuer.ts +92 -4
- package/src/rate-limiter.ts +56 -32
- package/src/throttler.ts +45 -53
- package/src/types.ts +2 -10
- package/src/utils.ts +1 -1
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
|
|
|
@@ -41,37 +58,41 @@ const defaultOptions: Required<AsyncThrottlerOptions<any, any>> = {
|
|
|
41
58
|
* Unlike debouncing which resets the delay timer on each call, throttling ensures the function executes at a
|
|
42
59
|
* regular interval regardless of how often it's called.
|
|
43
60
|
*
|
|
61
|
+
* Unlike the non-async Throttler, this async version supports returning values from the throttled function,
|
|
62
|
+
* making it ideal for API calls and other async operations where you want the result of the `maybeExecute` call
|
|
63
|
+
* instead of setting the result on a state variable from within the throttled function.
|
|
64
|
+
*
|
|
44
65
|
* This is useful for rate-limiting API calls, handling scroll/resize events, or any scenario where you want to
|
|
45
66
|
* ensure a maximum execution frequency.
|
|
46
67
|
*
|
|
47
68
|
* @example
|
|
48
69
|
* ```ts
|
|
49
70
|
* const throttler = new AsyncThrottler(async (value: string) => {
|
|
50
|
-
* await saveToAPI(value);
|
|
71
|
+
* const result = await saveToAPI(value);
|
|
72
|
+
* return result; // Return value is preserved
|
|
51
73
|
* }, { wait: 1000 });
|
|
52
74
|
*
|
|
53
75
|
* // Will only execute once per second no matter how often called
|
|
54
|
-
*
|
|
55
|
-
*
|
|
56
|
-
* });
|
|
76
|
+
* // Returns the API response directly
|
|
77
|
+
* const result = await throttler.maybeExecute(inputElement.value);
|
|
57
78
|
* ```
|
|
58
79
|
*/
|
|
59
|
-
export class AsyncThrottler<
|
|
60
|
-
|
|
61
|
-
TArgs extends Parameters<TFn>,
|
|
62
|
-
> {
|
|
63
|
-
private _options: Required<AsyncThrottlerOptions<TFn, TArgs>>
|
|
80
|
+
export class AsyncThrottler<TFn extends AnyAsyncFunction> {
|
|
81
|
+
private _options: Required<AsyncThrottlerOptions<TFn>>
|
|
64
82
|
private _abortController: AbortController | null = null
|
|
65
|
-
private
|
|
83
|
+
private _errorCount = 0
|
|
66
84
|
private _isExecuting = false
|
|
67
|
-
private
|
|
68
|
-
private _lastArgs: TArgs | undefined
|
|
85
|
+
private _lastArgs: Parameters<TFn> | undefined
|
|
69
86
|
private _lastExecutionTime = 0
|
|
87
|
+
private _lastResult: ReturnType<TFn> | undefined
|
|
70
88
|
private _nextExecutionTime = 0
|
|
89
|
+
private _settleCount = 0
|
|
90
|
+
private _successCount = 0
|
|
91
|
+
private _timeoutId: NodeJS.Timeout | null = null
|
|
71
92
|
|
|
72
93
|
constructor(
|
|
73
94
|
private fn: TFn,
|
|
74
|
-
initialOptions: AsyncThrottlerOptions<TFn
|
|
95
|
+
initialOptions: AsyncThrottlerOptions<TFn>,
|
|
75
96
|
) {
|
|
76
97
|
this._options = {
|
|
77
98
|
...defaultOptions,
|
|
@@ -83,20 +104,19 @@ export class AsyncThrottler<
|
|
|
83
104
|
* Updates the throttler options
|
|
84
105
|
* Returns the new options state
|
|
85
106
|
*/
|
|
86
|
-
setOptions(
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
|
|
107
|
+
setOptions(newOptions: Partial<AsyncThrottlerOptions<TFn>>): void {
|
|
108
|
+
this._options = { ...this._options, ...newOptions }
|
|
109
|
+
|
|
110
|
+
// End the pending state if the debouncer is disabled
|
|
111
|
+
if (!this._options.enabled) {
|
|
112
|
+
this.cancel()
|
|
92
113
|
}
|
|
93
|
-
return this._options
|
|
94
114
|
}
|
|
95
115
|
|
|
96
116
|
/**
|
|
97
117
|
* Returns the current options
|
|
98
118
|
*/
|
|
99
|
-
getOptions(): Required<AsyncThrottlerOptions<TFn
|
|
119
|
+
getOptions(): Required<AsyncThrottlerOptions<TFn>> {
|
|
100
120
|
return this._options
|
|
101
121
|
}
|
|
102
122
|
|
|
@@ -104,84 +124,82 @@ export class AsyncThrottler<
|
|
|
104
124
|
* Attempts to execute the throttled function
|
|
105
125
|
* If a call is already in progress, it may be blocked or queued depending on the `wait` option
|
|
106
126
|
*/
|
|
107
|
-
async maybeExecute(
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
|
|
127
|
+
async maybeExecute(
|
|
128
|
+
...args: Parameters<TFn>
|
|
129
|
+
): Promise<ReturnType<TFn> | undefined> {
|
|
130
|
+
const now = Date.now()
|
|
131
|
+
const timeSinceLastExecution = now - this._lastExecutionTime
|
|
111
132
|
|
|
112
|
-
|
|
113
|
-
|
|
133
|
+
// Handle leading execution
|
|
134
|
+
if (this._options.leading && timeSinceLastExecution >= this._options.wait) {
|
|
135
|
+
await this.executeFunction(...args)
|
|
136
|
+
return this._lastResult
|
|
137
|
+
} else {
|
|
138
|
+
// Store the most recent arguments for potential trailing execution
|
|
139
|
+
this._lastArgs = args
|
|
114
140
|
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
|
|
141
|
+
return new Promise((resolve) => {
|
|
142
|
+
// Clear any existing timeout to ensure we use the latest arguments
|
|
143
|
+
if (this._timeoutId) {
|
|
144
|
+
clearTimeout(this._timeoutId)
|
|
145
|
+
}
|
|
119
146
|
|
|
120
|
-
|
|
121
|
-
|
|
122
|
-
|
|
147
|
+
// Set up trailing execution if enabled
|
|
148
|
+
if (this._options.trailing) {
|
|
149
|
+
const _timeSinceLastExecution = this._lastExecutionTime
|
|
150
|
+
? now - this._lastExecutionTime
|
|
151
|
+
: 0
|
|
152
|
+
const timeoutDuration = this._options.wait - _timeSinceLastExecution
|
|
153
|
+
this._timeoutId = setTimeout(async () => {
|
|
154
|
+
if (this._lastArgs !== undefined) {
|
|
155
|
+
await this.executeFunction(...this._lastArgs)
|
|
156
|
+
}
|
|
157
|
+
resolve(this._lastResult)
|
|
158
|
+
}, timeoutDuration)
|
|
159
|
+
}
|
|
160
|
+
})
|
|
161
|
+
}
|
|
162
|
+
}
|
|
123
163
|
|
|
124
|
-
|
|
164
|
+
private async executeFunction(
|
|
165
|
+
...args: Parameters<TFn>
|
|
166
|
+
): Promise<ReturnType<TFn> | undefined> {
|
|
167
|
+
if (!this._options.enabled || this._isExecuting) return undefined
|
|
168
|
+
this._abortController = new AbortController()
|
|
169
|
+
try {
|
|
125
170
|
this._isExecuting = true
|
|
126
|
-
|
|
127
|
-
|
|
171
|
+
this._lastResult = await this.fn(...args) // EXECUTE!
|
|
172
|
+
this._successCount++
|
|
173
|
+
this._options.onSuccess(this._lastResult!, this)
|
|
128
174
|
} catch (error) {
|
|
129
|
-
|
|
130
|
-
|
|
131
|
-
}
|
|
132
|
-
try {
|
|
133
|
-
this._options.onError(error)
|
|
134
|
-
} catch {
|
|
135
|
-
// Ignore errors from error handler
|
|
136
|
-
}
|
|
175
|
+
this._errorCount++
|
|
176
|
+
this._options.onError(error, this)
|
|
137
177
|
} finally {
|
|
138
|
-
this._lastExecutionTime = Date.now()
|
|
139
|
-
this._nextExecutionTime = this._lastExecutionTime + this._options.wait
|
|
140
178
|
this._isExecuting = false
|
|
179
|
+
this._settleCount++
|
|
141
180
|
this._abortController = null
|
|
181
|
+
this._lastExecutionTime = Date.now()
|
|
182
|
+
this._nextExecutionTime = this._lastExecutionTime + this._options.wait
|
|
183
|
+
this._options.onSettled(this)
|
|
142
184
|
}
|
|
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)
|
|
185
|
+
return this._lastResult
|
|
164
186
|
}
|
|
165
187
|
|
|
166
188
|
/**
|
|
167
|
-
* Cancels any pending execution
|
|
189
|
+
* Cancels any pending execution or aborts any execution in progress
|
|
168
190
|
*/
|
|
169
191
|
cancel(): void {
|
|
192
|
+
if (this._timeoutId) {
|
|
193
|
+
clearTimeout(this._timeoutId)
|
|
194
|
+
this._timeoutId = null
|
|
195
|
+
}
|
|
170
196
|
if (this._abortController) {
|
|
171
197
|
this._abortController.abort()
|
|
172
198
|
this._abortController = null
|
|
173
199
|
}
|
|
174
|
-
this._isPending = false
|
|
175
200
|
this._lastArgs = undefined
|
|
176
201
|
}
|
|
177
202
|
|
|
178
|
-
/**
|
|
179
|
-
* Returns the number of times the function has been executed
|
|
180
|
-
*/
|
|
181
|
-
getExecutionCount(): number {
|
|
182
|
-
return this._executionCount
|
|
183
|
-
}
|
|
184
|
-
|
|
185
203
|
/**
|
|
186
204
|
* Returns the last execution time
|
|
187
205
|
*/
|
|
@@ -196,11 +214,46 @@ export class AsyncThrottler<
|
|
|
196
214
|
return this._nextExecutionTime
|
|
197
215
|
}
|
|
198
216
|
|
|
217
|
+
/**
|
|
218
|
+
* Returns the last result of the debounced function
|
|
219
|
+
*/
|
|
220
|
+
getLastResult(): ReturnType<TFn> | undefined {
|
|
221
|
+
return this._lastResult
|
|
222
|
+
}
|
|
223
|
+
|
|
224
|
+
/**
|
|
225
|
+
* Returns the number of times the function has been executed successfully
|
|
226
|
+
*/
|
|
227
|
+
getSuccessCount(): number {
|
|
228
|
+
return this._successCount
|
|
229
|
+
}
|
|
230
|
+
|
|
231
|
+
/**
|
|
232
|
+
* Returns the number of times the function has settled (completed or errored)
|
|
233
|
+
*/
|
|
234
|
+
getSettleCount(): number {
|
|
235
|
+
return this._settleCount
|
|
236
|
+
}
|
|
237
|
+
|
|
238
|
+
/**
|
|
239
|
+
* Returns the number of times the function has errored
|
|
240
|
+
*/
|
|
241
|
+
getErrorCount(): number {
|
|
242
|
+
return this._errorCount
|
|
243
|
+
}
|
|
244
|
+
|
|
199
245
|
/**
|
|
200
246
|
* Returns the current pending state
|
|
201
247
|
*/
|
|
202
248
|
getIsPending(): boolean {
|
|
203
|
-
return this.
|
|
249
|
+
return this._options.enabled && !!this._timeoutId
|
|
250
|
+
}
|
|
251
|
+
|
|
252
|
+
/**
|
|
253
|
+
* Returns the current executing state
|
|
254
|
+
*/
|
|
255
|
+
getIsExecuting(): boolean {
|
|
256
|
+
return this._isExecuting
|
|
204
257
|
}
|
|
205
258
|
}
|
|
206
259
|
|
|
@@ -209,21 +262,26 @@ export class AsyncThrottler<
|
|
|
209
262
|
* The throttled function will execute at most once per wait period, even if called multiple times.
|
|
210
263
|
* If called while executing, it will wait until execution completes before scheduling the next call.
|
|
211
264
|
*
|
|
265
|
+
* Unlike the non-async Throttler, this async version supports returning values from the throttled function,
|
|
266
|
+
* making it ideal for API calls and other async operations where you want the result of the `maybeExecute` call
|
|
267
|
+
* instead of setting the result on a state variable from within the throttled function.
|
|
268
|
+
*
|
|
212
269
|
* @example
|
|
213
270
|
* ```ts
|
|
214
|
-
* const throttled = asyncThrottle(async () => {
|
|
215
|
-
* await
|
|
271
|
+
* const throttled = asyncThrottle(async (value: string) => {
|
|
272
|
+
* const result = await saveToAPI(value);
|
|
273
|
+
* return result; // Return value is preserved
|
|
216
274
|
* }, { wait: 1000 });
|
|
217
275
|
*
|
|
218
276
|
* // This will execute at most once per second
|
|
219
|
-
*
|
|
220
|
-
* await throttled();
|
|
277
|
+
* // Returns the API response directly
|
|
278
|
+
* const result = await throttled(inputElement.value);
|
|
221
279
|
* ```
|
|
222
280
|
*/
|
|
223
|
-
export function asyncThrottle<
|
|
224
|
-
TFn
|
|
225
|
-
|
|
226
|
-
|
|
281
|
+
export function asyncThrottle<TFn extends AnyAsyncFunction>(
|
|
282
|
+
fn: TFn,
|
|
283
|
+
initialOptions: Omit<AsyncThrottlerOptions<TFn>, 'enabled'>,
|
|
284
|
+
) {
|
|
227
285
|
const asyncThrottler = new AsyncThrottler(fn, initialOptions)
|
|
228
286
|
return asyncThrottler.maybeExecute.bind(asyncThrottler)
|
|
229
287
|
}
|
package/src/debouncer.ts
CHANGED
|
@@ -3,10 +3,7 @@ import type { AnyFunction } from './types'
|
|
|
3
3
|
/**
|
|
4
4
|
* Options for configuring a debounced function
|
|
5
5
|
*/
|
|
6
|
-
export interface DebouncerOptions<
|
|
7
|
-
TFn extends AnyFunction,
|
|
8
|
-
TArgs extends Parameters<TFn>,
|
|
9
|
-
> {
|
|
6
|
+
export interface DebouncerOptions<TFn extends AnyFunction> {
|
|
10
7
|
/**
|
|
11
8
|
* Whether the debouncer is enabled. When disabled, maybeExecute will not trigger any executions.
|
|
12
9
|
* Defaults to true.
|
|
@@ -14,13 +11,14 @@ export interface DebouncerOptions<
|
|
|
14
11
|
enabled?: boolean
|
|
15
12
|
/**
|
|
16
13
|
* Whether to execute on the leading edge of the timeout.
|
|
14
|
+
* The first call will execute immediately and the rest will wait the delay.
|
|
17
15
|
* Defaults to false.
|
|
18
16
|
*/
|
|
19
17
|
leading?: boolean
|
|
20
18
|
/**
|
|
21
19
|
* Callback function that is called after the function is executed
|
|
22
20
|
*/
|
|
23
|
-
onExecute?: (debouncer: Debouncer<TFn
|
|
21
|
+
onExecute?: (debouncer: Debouncer<TFn>) => void
|
|
24
22
|
/**
|
|
25
23
|
* Whether to execute on the trailing edge of the timeout.
|
|
26
24
|
* Defaults to true.
|
|
@@ -33,12 +31,12 @@ export interface DebouncerOptions<
|
|
|
33
31
|
wait: number
|
|
34
32
|
}
|
|
35
33
|
|
|
36
|
-
const defaultOptions: Required<DebouncerOptions<any
|
|
34
|
+
const defaultOptions: Required<DebouncerOptions<any>> = {
|
|
37
35
|
enabled: true,
|
|
38
36
|
leading: false,
|
|
37
|
+
onExecute: () => {},
|
|
39
38
|
trailing: true,
|
|
40
39
|
wait: 0,
|
|
41
|
-
onExecute: () => {},
|
|
42
40
|
}
|
|
43
41
|
|
|
44
42
|
/**
|
|
@@ -64,16 +62,16 @@ const defaultOptions: Required<DebouncerOptions<any, any>> = {
|
|
|
64
62
|
* });
|
|
65
63
|
* ```
|
|
66
64
|
*/
|
|
67
|
-
export class Debouncer<TFn extends AnyFunction
|
|
65
|
+
export class Debouncer<TFn extends AnyFunction> {
|
|
68
66
|
private _canLeadingExecute = true
|
|
69
|
-
private _isPending = false
|
|
70
67
|
private _executionCount = 0
|
|
71
|
-
private
|
|
68
|
+
private _isPending = false
|
|
69
|
+
private _options: Required<DebouncerOptions<TFn>>
|
|
72
70
|
private _timeoutId: NodeJS.Timeout | undefined
|
|
73
71
|
|
|
74
72
|
constructor(
|
|
75
73
|
private fn: TFn,
|
|
76
|
-
initialOptions: DebouncerOptions<TFn
|
|
74
|
+
initialOptions: DebouncerOptions<TFn>,
|
|
77
75
|
) {
|
|
78
76
|
this._options = {
|
|
79
77
|
...defaultOptions,
|
|
@@ -85,26 +83,19 @@ export class Debouncer<TFn extends AnyFunction, TArgs extends Parameters<TFn>> {
|
|
|
85
83
|
* Updates the debouncer options
|
|
86
84
|
* Returns the new options state
|
|
87
85
|
*/
|
|
88
|
-
setOptions(
|
|
89
|
-
|
|
90
|
-
): Required<DebouncerOptions<TFn, TArgs>> {
|
|
91
|
-
this._options = {
|
|
92
|
-
...this._options,
|
|
93
|
-
...newOptions,
|
|
94
|
-
}
|
|
86
|
+
setOptions(newOptions: Partial<DebouncerOptions<TFn>>): void {
|
|
87
|
+
this._options = { ...this._options, ...newOptions }
|
|
95
88
|
|
|
96
89
|
// End the pending state if the debouncer is disabled
|
|
97
90
|
if (!this._options.enabled) {
|
|
98
91
|
this._isPending = false
|
|
99
92
|
}
|
|
100
|
-
|
|
101
|
-
return this._options
|
|
102
93
|
}
|
|
103
94
|
|
|
104
95
|
/**
|
|
105
96
|
* Returns the current debouncer options
|
|
106
97
|
*/
|
|
107
|
-
getOptions(): Required<DebouncerOptions<TFn
|
|
98
|
+
getOptions(): Required<DebouncerOptions<TFn>> {
|
|
108
99
|
return this._options
|
|
109
100
|
}
|
|
110
101
|
|
|
@@ -112,35 +103,37 @@ export class Debouncer<TFn extends AnyFunction, TArgs extends Parameters<TFn>> {
|
|
|
112
103
|
* Attempts to execute the debounced function
|
|
113
104
|
* If a call is already in progress, it will be queued
|
|
114
105
|
*/
|
|
115
|
-
maybeExecute(...args:
|
|
106
|
+
maybeExecute(...args: Parameters<TFn>): void {
|
|
107
|
+
let _didLeadingExecute = false
|
|
108
|
+
|
|
116
109
|
// Handle leading execution
|
|
117
110
|
if (this._options.leading && this._canLeadingExecute) {
|
|
118
|
-
this.executeFunction(...args)
|
|
119
111
|
this._canLeadingExecute = false
|
|
112
|
+
_didLeadingExecute = true
|
|
113
|
+
this.executeFunction(...args)
|
|
120
114
|
}
|
|
121
115
|
|
|
122
|
-
// Start pending state
|
|
123
|
-
if (this._options.
|
|
116
|
+
// Start pending state to indicate that the debouncer is waiting for the trailing edge
|
|
117
|
+
if (this._options.trailing) {
|
|
124
118
|
this._isPending = true
|
|
125
119
|
}
|
|
126
120
|
|
|
127
121
|
// Clear any existing timeout
|
|
128
122
|
if (this._timeoutId) clearTimeout(this._timeoutId)
|
|
129
123
|
|
|
130
|
-
// Set new timeout that will reset canLeadingExecute
|
|
124
|
+
// Set new timeout that will reset canLeadingExecute and execute trailing only if enabled and did not execute leading
|
|
131
125
|
this._timeoutId = setTimeout(() => {
|
|
132
126
|
this._canLeadingExecute = true
|
|
133
|
-
this.
|
|
134
|
-
// Execute trailing only if enabled
|
|
135
|
-
if (this._options.trailing) {
|
|
127
|
+
if (this._options.trailing && !_didLeadingExecute) {
|
|
136
128
|
this.executeFunction(...args)
|
|
137
129
|
}
|
|
138
130
|
}, this._options.wait)
|
|
139
131
|
}
|
|
140
132
|
|
|
141
|
-
private executeFunction(...args:
|
|
142
|
-
if (!this._options.enabled) return
|
|
133
|
+
private executeFunction(...args: Parameters<TFn>): void {
|
|
134
|
+
if (!this._options.enabled) return undefined
|
|
143
135
|
this.fn(...args) // EXECUTE!
|
|
136
|
+
this._isPending = false
|
|
144
137
|
this._executionCount++
|
|
145
138
|
this._options.onExecute(this)
|
|
146
139
|
}
|
|
@@ -193,8 +186,8 @@ export class Debouncer<TFn extends AnyFunction, TArgs extends Parameters<TFn>> {
|
|
|
193
186
|
*/
|
|
194
187
|
export function debounce<TFn extends AnyFunction>(
|
|
195
188
|
fn: TFn,
|
|
196
|
-
initialOptions: Omit<DebouncerOptions<TFn
|
|
197
|
-
) {
|
|
189
|
+
initialOptions: Omit<DebouncerOptions<TFn>, 'enabled'>,
|
|
190
|
+
): (...args: Parameters<TFn>) => void {
|
|
198
191
|
const debouncer = new Debouncer(fn, initialOptions)
|
|
199
192
|
return debouncer.maybeExecute.bind(debouncer)
|
|
200
193
|
}
|