@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.
Files changed (91) hide show
  1. package/dist/cjs/async-batcher.cjs +163 -0
  2. package/dist/cjs/async-batcher.cjs.map +1 -0
  3. package/dist/cjs/async-batcher.d.cts +273 -0
  4. package/dist/cjs/async-debouncer.cjs +149 -162
  5. package/dist/cjs/async-debouncer.cjs.map +1 -1
  6. package/dist/cjs/async-debouncer.d.cts +76 -57
  7. package/dist/cjs/async-queuer.cjs +282 -343
  8. package/dist/cjs/async-queuer.cjs.map +1 -1
  9. package/dist/cjs/async-queuer.d.cts +123 -102
  10. package/dist/cjs/async-rate-limiter.cjs +128 -185
  11. package/dist/cjs/async-rate-limiter.cjs.map +1 -1
  12. package/dist/cjs/async-rate-limiter.d.cts +72 -61
  13. package/dist/cjs/async-throttler.cjs +168 -178
  14. package/dist/cjs/async-throttler.cjs.map +1 -1
  15. package/dist/cjs/async-throttler.d.cts +97 -69
  16. package/dist/cjs/batcher.cjs +110 -119
  17. package/dist/cjs/batcher.cjs.map +1 -1
  18. package/dist/cjs/batcher.d.cts +76 -51
  19. package/dist/cjs/debouncer.cjs +97 -85
  20. package/dist/cjs/debouncer.cjs.map +1 -1
  21. package/dist/cjs/debouncer.d.cts +54 -26
  22. package/dist/cjs/index.cjs +3 -6
  23. package/dist/cjs/index.cjs.map +1 -1
  24. package/dist/cjs/index.d.cts +1 -1
  25. package/dist/cjs/queuer.cjs +246 -294
  26. package/dist/cjs/queuer.cjs.map +1 -1
  27. package/dist/cjs/queuer.d.cts +105 -84
  28. package/dist/cjs/rate-limiter.cjs +97 -130
  29. package/dist/cjs/rate-limiter.cjs.map +1 -1
  30. package/dist/cjs/rate-limiter.d.cts +50 -37
  31. package/dist/cjs/throttler.cjs +107 -123
  32. package/dist/cjs/throttler.cjs.map +1 -1
  33. package/dist/cjs/throttler.d.cts +59 -35
  34. package/dist/cjs/utils.cjs +0 -13
  35. package/dist/cjs/utils.cjs.map +1 -1
  36. package/dist/cjs/utils.d.cts +0 -1
  37. package/dist/esm/async-batcher.d.ts +273 -0
  38. package/dist/esm/async-batcher.js +163 -0
  39. package/dist/esm/async-batcher.js.map +1 -0
  40. package/dist/esm/async-debouncer.d.ts +76 -57
  41. package/dist/esm/async-debouncer.js +149 -162
  42. package/dist/esm/async-debouncer.js.map +1 -1
  43. package/dist/esm/async-queuer.d.ts +123 -102
  44. package/dist/esm/async-queuer.js +282 -343
  45. package/dist/esm/async-queuer.js.map +1 -1
  46. package/dist/esm/async-rate-limiter.d.ts +72 -61
  47. package/dist/esm/async-rate-limiter.js +128 -185
  48. package/dist/esm/async-rate-limiter.js.map +1 -1
  49. package/dist/esm/async-throttler.d.ts +97 -69
  50. package/dist/esm/async-throttler.js +168 -178
  51. package/dist/esm/async-throttler.js.map +1 -1
  52. package/dist/esm/batcher.d.ts +76 -51
  53. package/dist/esm/batcher.js +110 -119
  54. package/dist/esm/batcher.js.map +1 -1
  55. package/dist/esm/debouncer.d.ts +54 -26
  56. package/dist/esm/debouncer.js +97 -85
  57. package/dist/esm/debouncer.js.map +1 -1
  58. package/dist/esm/index.d.ts +1 -1
  59. package/dist/esm/index.js +4 -7
  60. package/dist/esm/queuer.d.ts +105 -84
  61. package/dist/esm/queuer.js +246 -294
  62. package/dist/esm/queuer.js.map +1 -1
  63. package/dist/esm/rate-limiter.d.ts +50 -37
  64. package/dist/esm/rate-limiter.js +97 -130
  65. package/dist/esm/rate-limiter.js.map +1 -1
  66. package/dist/esm/throttler.d.ts +59 -35
  67. package/dist/esm/throttler.js +107 -123
  68. package/dist/esm/throttler.js.map +1 -1
  69. package/dist/esm/utils.d.ts +0 -1
  70. package/dist/esm/utils.js +0 -13
  71. package/dist/esm/utils.js.map +1 -1
  72. package/package.json +14 -11
  73. package/src/async-batcher.ts +475 -0
  74. package/src/async-debouncer.ts +201 -121
  75. package/src/async-queuer.ts +341 -220
  76. package/src/async-rate-limiter.ts +176 -136
  77. package/src/async-throttler.ts +233 -139
  78. package/src/batcher.ts +159 -93
  79. package/src/debouncer.ts +135 -52
  80. package/src/index.ts +1 -1
  81. package/src/queuer.ts +349 -227
  82. package/src/rate-limiter.ts +125 -80
  83. package/src/throttler.ts +152 -78
  84. package/src/utils.ts +0 -15
  85. package/dist/cjs/compare.cjs +0 -72
  86. package/dist/cjs/compare.cjs.map +0 -1
  87. package/dist/cjs/compare.d.cts +0 -12
  88. package/dist/esm/compare.d.ts +0 -12
  89. package/dist/esm/compare.js +0 -72
  90. package/dist/esm/compare.js.map +0 -1
  91. package/src/compare.ts +0 -105
@@ -1,6 +1,20 @@
1
1
  "use strict";
2
2
  Object.defineProperty(exports, Symbol.toStringTag, { value: "Module" });
3
+ const store = require("@tanstack/store");
3
4
  const utils = require("./utils.cjs");
5
+ function getDefaultAsyncDebouncerState() {
6
+ return structuredClone({
7
+ canLeadingExecute: true,
8
+ errorCount: 0,
9
+ isExecuting: false,
10
+ isPending: false,
11
+ lastArgs: void 0,
12
+ lastResult: void 0,
13
+ settleCount: 0,
14
+ successCount: 0,
15
+ status: "idle"
16
+ });
17
+ }
4
18
  const defaultOptions = {
5
19
  enabled: true,
6
20
  leading: false,
@@ -10,176 +24,149 @@ const defaultOptions = {
10
24
  class AsyncDebouncer {
11
25
  constructor(fn, initialOptions) {
12
26
  this.fn = fn;
13
- this._abortController = null;
14
- this._canLeadingExecute = true;
15
- this._errorCount = 0;
16
- this._isExecuting = false;
17
- this._isPending = false;
18
- this._settleCount = 0;
19
- this._successCount = 0;
20
- this._timeoutId = null;
21
- this._resolvePreviousPromise = null;
22
- this._options = {
27
+ this.store = new store.Store(getDefaultAsyncDebouncerState());
28
+ this.#abortController = null;
29
+ this.#timeoutId = null;
30
+ this.#resolvePreviousPromise = null;
31
+ this.setOptions = (newOptions) => {
32
+ this.options = { ...this.options, ...newOptions };
33
+ if (!this.#getEnabled()) {
34
+ this.cancel();
35
+ }
36
+ };
37
+ this.#setState = (newState) => {
38
+ this.store.setState((state) => {
39
+ const combinedState = {
40
+ ...state,
41
+ ...newState
42
+ };
43
+ const { isPending, isExecuting, settleCount } = combinedState;
44
+ return {
45
+ ...combinedState,
46
+ status: !this.#getEnabled() ? "disabled" : isPending ? "pending" : isExecuting ? "executing" : settleCount > 0 ? "settled" : "idle"
47
+ };
48
+ });
49
+ };
50
+ this.#getEnabled = () => {
51
+ return !!utils.parseFunctionOrValue(this.options.enabled, this);
52
+ };
53
+ this.#getWait = () => {
54
+ return utils.parseFunctionOrValue(this.options.wait, this);
55
+ };
56
+ this.maybeExecute = async (...args) => {
57
+ if (!this.#getEnabled()) return void 0;
58
+ this.#cancelPendingExecution();
59
+ this.#setState({ lastArgs: args });
60
+ if (this.options.leading && this.store.state.canLeadingExecute) {
61
+ this.#setState({ canLeadingExecute: false });
62
+ await this.#execute(...args);
63
+ return this.store.state.lastResult;
64
+ }
65
+ if (this.options.trailing && this.#getEnabled()) {
66
+ this.#setState({ isPending: true });
67
+ }
68
+ return new Promise((resolve) => {
69
+ this.#resolvePreviousPromise = resolve;
70
+ this.#timeoutId = setTimeout(async () => {
71
+ if (this.options.trailing && this.store.state.lastArgs) {
72
+ await this.#execute(...this.store.state.lastArgs);
73
+ }
74
+ this.#setState({ canLeadingExecute: true });
75
+ this.#resolvePreviousPromise = null;
76
+ resolve(this.store.state.lastResult);
77
+ }, this.#getWait());
78
+ });
79
+ };
80
+ this.#execute = async (...args) => {
81
+ if (!this.#getEnabled()) return void 0;
82
+ this.#abortController = new AbortController();
83
+ try {
84
+ this.#setState({ isExecuting: true });
85
+ const result = await this.fn(...args);
86
+ this.#setState({
87
+ lastResult: result,
88
+ successCount: this.store.state.successCount + 1
89
+ });
90
+ this.options.onSuccess?.(result, this);
91
+ } catch (error) {
92
+ this.#setState({
93
+ errorCount: this.store.state.errorCount + 1
94
+ });
95
+ this.options.onError?.(error, this);
96
+ if (this.options.throwOnError) {
97
+ throw error;
98
+ }
99
+ } finally {
100
+ this.#setState({
101
+ isExecuting: false,
102
+ isPending: false,
103
+ settleCount: this.store.state.settleCount + 1
104
+ });
105
+ this.#abortController = null;
106
+ this.options.onSettled?.(this);
107
+ }
108
+ return this.store.state.lastResult;
109
+ };
110
+ this.flush = () => {
111
+ if (this.store.state.isPending && this.store.state.lastArgs) {
112
+ this.#abortExecution();
113
+ this.#clearTimeout();
114
+ this.#execute(...this.store.state.lastArgs);
115
+ }
116
+ };
117
+ this.#clearTimeout = () => {
118
+ if (this.#timeoutId) {
119
+ clearTimeout(this.#timeoutId);
120
+ this.#timeoutId = null;
121
+ }
122
+ };
123
+ this.#cancelPendingExecution = () => {
124
+ this.#clearTimeout();
125
+ if (this.#resolvePreviousPromise) {
126
+ this.#resolvePreviousPromise(this.store.state.lastResult);
127
+ this.#resolvePreviousPromise = null;
128
+ }
129
+ this.#setState({
130
+ isPending: false,
131
+ isExecuting: false,
132
+ lastArgs: void 0
133
+ });
134
+ };
135
+ this.#abortExecution = () => {
136
+ if (this.#abortController) {
137
+ this.#abortController.abort();
138
+ this.#abortController = null;
139
+ }
140
+ };
141
+ this.cancel = () => {
142
+ this.#cancelPendingExecution();
143
+ this.#abortExecution();
144
+ this.#setState({ canLeadingExecute: true });
145
+ };
146
+ this.reset = () => {
147
+ this.#setState(getDefaultAsyncDebouncerState());
148
+ };
149
+ this.options = {
23
150
  ...defaultOptions,
24
151
  ...initialOptions,
25
152
  throwOnError: initialOptions.throwOnError ?? !initialOptions.onError
26
153
  };
154
+ this.#setState(this.options.initialState ?? {});
27
155
  }
28
- /**
29
- * Updates the debouncer options
30
- */
31
- setOptions(newOptions) {
32
- this._options = { ...this._options, ...newOptions };
33
- if (!this._options.enabled) {
34
- this._isPending = false;
35
- }
36
- }
37
- /**
38
- * Returns the current debouncer options
39
- */
40
- getOptions() {
41
- return this._options;
42
- }
43
- /**
44
- * Returns the current debouncer enabled state
45
- */
46
- getEnabled() {
47
- return !!utils.parseFunctionOrValue(this._options.enabled, this);
48
- }
49
- /**
50
- * Returns the current debouncer wait state
51
- */
52
- getWait() {
53
- return utils.parseFunctionOrValue(this._options.wait, this);
54
- }
55
- /**
56
- * Attempts to execute the debounced function.
57
- * If a call is already in progress, it will be queued.
58
- *
59
- * Error Handling:
60
- * - If the debounced function throws and no `onError` handler is configured,
61
- * the error will be thrown from this method.
62
- * - If an `onError` handler is configured, errors will be caught and passed to the handler,
63
- * and this method will return undefined.
64
- * - The error state can be checked using `getErrorCount()` and `getIsExecuting()`.
65
- *
66
- * @returns A promise that resolves with the function's return value, or undefined if an error occurred and was handled by onError
67
- * @throws The error from the debounced function if no onError handler is configured
68
- */
69
- async maybeExecute(...args) {
70
- this._cancel();
71
- this._lastArgs = args;
72
- if (this._options.leading && this._canLeadingExecute) {
73
- this._canLeadingExecute = false;
74
- await this.execute(...args);
75
- return this._lastResult;
76
- }
77
- if (this._options.trailing) {
78
- this._isPending = true;
79
- }
80
- return new Promise((resolve) => {
81
- this._resolvePreviousPromise = resolve;
82
- this._timeoutId = setTimeout(async () => {
83
- if (this._options.trailing && this._lastArgs) {
84
- await this.execute(...this._lastArgs);
85
- }
86
- this._canLeadingExecute = true;
87
- this._resolvePreviousPromise = null;
88
- resolve(this._lastResult);
89
- }, this.getWait());
90
- });
91
- }
92
- async execute(...args) {
93
- var _a, _b, _c, _d, _e, _f;
94
- if (!this.getEnabled()) return void 0;
95
- this._abortController = new AbortController();
96
- try {
97
- this._isExecuting = true;
98
- this._lastResult = await this.fn(...args);
99
- this._successCount++;
100
- (_b = (_a = this._options).onSuccess) == null ? void 0 : _b.call(_a, this._lastResult, this);
101
- } catch (error) {
102
- this._errorCount++;
103
- (_d = (_c = this._options).onError) == null ? void 0 : _d.call(_c, error, this);
104
- if (this._options.throwOnError) {
105
- throw error;
106
- }
107
- } finally {
108
- this._isExecuting = false;
109
- this._isPending = false;
110
- this._settleCount++;
111
- this._abortController = null;
112
- (_f = (_e = this._options).onSettled) == null ? void 0 : _f.call(_e, this);
113
- }
114
- return this._lastResult;
115
- }
116
- /**
117
- * Cancel without resetting _canLeadingExecute
118
- */
119
- _cancel() {
120
- if (this._timeoutId) {
121
- clearTimeout(this._timeoutId);
122
- this._timeoutId = null;
123
- }
124
- if (this._abortController) {
125
- this._abortController.abort();
126
- this._abortController = null;
127
- }
128
- if (this._resolvePreviousPromise) {
129
- this._resolvePreviousPromise(this._lastResult);
130
- this._resolvePreviousPromise = null;
131
- }
132
- this._lastArgs = void 0;
133
- this._isPending = false;
134
- this._isExecuting = false;
135
- }
136
- /**
137
- * Cancels any pending execution or aborts any execution in progress
138
- */
139
- cancel() {
140
- this._canLeadingExecute = true;
141
- this._cancel();
142
- }
143
- /**
144
- * Returns the last result of the debounced function
145
- */
146
- getLastResult() {
147
- return this._lastResult;
148
- }
149
- /**
150
- * Returns the number of times the function has been executed successfully
151
- */
152
- getSuccessCount() {
153
- return this._successCount;
154
- }
155
- /**
156
- * Returns the number of times the function has settled (completed or errored)
157
- */
158
- getSettleCount() {
159
- return this._settleCount;
160
- }
161
- /**
162
- * Returns the number of times the function has errored
163
- */
164
- getErrorCount() {
165
- return this._errorCount;
166
- }
167
- /**
168
- * Returns `true` if there is a pending execution queued up for trailing execution
169
- */
170
- getIsPending() {
171
- return this.getEnabled() && this._isPending;
172
- }
173
- /**
174
- * Returns `true` if there is currently an execution in progress
175
- */
176
- getIsExecuting() {
177
- return this._isExecuting;
178
- }
156
+ #abortController;
157
+ #timeoutId;
158
+ #resolvePreviousPromise;
159
+ #setState;
160
+ #getEnabled;
161
+ #getWait;
162
+ #execute;
163
+ #clearTimeout;
164
+ #cancelPendingExecution;
165
+ #abortExecution;
179
166
  }
180
167
  function asyncDebounce(fn, initialOptions) {
181
168
  const asyncDebouncer = new AsyncDebouncer(fn, initialOptions);
182
- return asyncDebouncer.maybeExecute.bind(asyncDebouncer);
169
+ return asyncDebouncer.maybeExecute;
183
170
  }
184
171
  exports.AsyncDebouncer = AsyncDebouncer;
185
172
  exports.asyncDebounce = asyncDebounce;
@@ -1 +1 @@
1
- {"version":3,"file":"async-debouncer.cjs","sources":["../../src/async-debouncer.ts"],"sourcesContent":["import { parseFunctionOrValue } from './utils'\nimport type { AnyAsyncFunction, OptionalKeys } from './types'\n\n/**\n * Options for configuring an async debounced function\n */\nexport interface AsyncDebouncerOptions<TFn extends AnyAsyncFunction> {\n /**\n * Whether the debouncer is enabled. When disabled, maybeExecute will not trigger any executions.\n * Can be a boolean or a function that returns a boolean.\n * Defaults to true.\n */\n enabled?: boolean | ((debouncer: AsyncDebouncer<TFn>) => boolean)\n /**\n * Whether to execute on the leading edge of the timeout.\n * Defaults to false.\n */\n leading?: boolean\n /**\n * Optional error handler for when the debounced function throws.\n * If provided, the handler will be called with the error and debouncer instance.\n * This can be used alongside throwOnError - the handler will be called before any error is thrown.\n */\n onError?: (error: unknown, debouncer: AsyncDebouncer<TFn>) => void\n /**\n * Optional callback to call when the debounced function is executed\n */\n onSettled?: (debouncer: AsyncDebouncer<TFn>) => void\n /**\n * Optional callback to call when the debounced function is executed\n */\n onSuccess?: (result: ReturnType<TFn>, debouncer: AsyncDebouncer<TFn>) => void\n /**\n * Whether to throw errors when they occur.\n * Defaults to true if no onError handler is provided, false if an onError handler is provided.\n * Can be explicitly set to override these defaults.\n */\n throwOnError?: boolean\n /**\n * Whether to execute on the trailing edge of the timeout.\n * Defaults to true.\n */\n trailing?: boolean\n /**\n * Delay in milliseconds to wait after the last call before executing.\n * Can be a number or a function that returns a number.\n * Defaults to 0ms\n */\n wait: number | ((debouncer: AsyncDebouncer<TFn>) => number)\n}\n\ntype AsyncDebouncerOptionsWithOptionalCallbacks = OptionalKeys<\n AsyncDebouncerOptions<any>,\n 'onError' | 'onSettled' | 'onSuccess'\n>\n\nconst defaultOptions: AsyncDebouncerOptionsWithOptionalCallbacks = {\n enabled: true,\n leading: false,\n trailing: true,\n wait: 0,\n}\n\n/**\n * A class that creates an async debounced function.\n *\n * Debouncing ensures that a function is only executed after a specified delay has passed since its last invocation.\n * Each new invocation resets the delay timer. This is useful for handling frequent events like window resizing\n * or input changes where you only want to execute the handler after the events have stopped occurring.\n *\n * Unlike throttling which allows execution at regular intervals, debouncing prevents any execution until\n * the function stops being called for the specified delay period.\n *\n * Unlike the non-async Debouncer, this async version supports returning values from the debounced function,\n * making it ideal for API calls and other async operations where you want the result of the `maybeExecute` call\n * instead of setting the result on a state variable from within the debounced function.\n *\n * Error Handling:\n * - If an error occurs during execution and no `onError` handler is provided, the error will be thrown and propagate up to the caller.\n * - If an `onError` handler is provided, errors will be caught and passed to the handler instead of being thrown.\n * - The error count can be tracked using `getErrorCount()`.\n * - The debouncer maintains its state and can continue to be used after an error occurs.\n *\n * @example\n * ```ts\n * const asyncDebouncer = new AsyncDebouncer(async (value: string) => {\n * const results = await searchAPI(value);\n * return results; // Return value is preserved\n * }, {\n * wait: 500,\n * onError: (error) => {\n * console.error('Search failed:', error);\n * }\n * });\n *\n * // Called on each keystroke but only executes after 500ms of no typing\n * // Returns the API response directly\n * const results = await asyncDebouncer.maybeExecute(inputElement.value);\n * ```\n */\nexport class AsyncDebouncer<TFn extends AnyAsyncFunction> {\n private _options: AsyncDebouncerOptionsWithOptionalCallbacks\n private _abortController: AbortController | null = null\n private _canLeadingExecute = true\n private _errorCount = 0\n private _isExecuting = false\n private _isPending = false\n private _lastArgs: Parameters<TFn> | undefined\n private _lastResult: ReturnType<TFn> | undefined\n private _settleCount = 0\n private _successCount = 0\n private _timeoutId: NodeJS.Timeout | null = null\n private _resolvePreviousPromise:\n | ((value?: ReturnType<TFn> | undefined) => void)\n | null = null\n\n constructor(\n private fn: TFn,\n initialOptions: AsyncDebouncerOptions<TFn>,\n ) {\n this._options = {\n ...defaultOptions,\n ...initialOptions,\n throwOnError: initialOptions.throwOnError ?? !initialOptions.onError,\n }\n }\n\n /**\n * Updates the debouncer options\n */\n setOptions(newOptions: Partial<AsyncDebouncerOptions<TFn>>): void {\n this._options = { ...this._options, ...newOptions }\n\n // End the pending state if the debouncer is disabled\n if (!this._options.enabled) {\n this._isPending = false\n }\n }\n\n /**\n * Returns the current debouncer options\n */\n getOptions(): AsyncDebouncerOptions<TFn> {\n return this._options\n }\n\n /**\n * Returns the current debouncer enabled state\n */\n getEnabled(): boolean {\n return !!parseFunctionOrValue(this._options.enabled, this)\n }\n\n /**\n * Returns the current debouncer wait state\n */\n getWait(): number {\n return parseFunctionOrValue(this._options.wait, this)\n }\n\n /**\n * Attempts to execute the debounced function.\n * If a call is already in progress, it will be queued.\n *\n * Error Handling:\n * - If the debounced function throws and no `onError` handler is configured,\n * the error will be thrown from this method.\n * - If an `onError` handler is configured, errors will be caught and passed to the handler,\n * and this method will return undefined.\n * - The error state can be checked using `getErrorCount()` and `getIsExecuting()`.\n *\n * @returns A promise that resolves with the function's return value, or undefined if an error occurred and was handled by onError\n * @throws The error from the debounced function if no onError handler is configured\n */\n async maybeExecute(\n ...args: Parameters<TFn>\n ): Promise<ReturnType<TFn> | undefined> {\n this._cancel()\n this._lastArgs = args\n\n // Handle leading execution\n if (this._options.leading && this._canLeadingExecute) {\n this._canLeadingExecute = false\n await this.execute(...args)\n return this._lastResult\n }\n\n // Handle trailing execution\n if (this._options.trailing) {\n this._isPending = true\n }\n\n return new Promise((resolve) => {\n this._resolvePreviousPromise = resolve\n this._timeoutId = setTimeout(async () => {\n // Execute trailing if enabled\n if (this._options.trailing && this._lastArgs) {\n await this.execute(...this._lastArgs)\n }\n\n // Reset state and resolve\n this._canLeadingExecute = true\n this._resolvePreviousPromise = null\n resolve(this._lastResult)\n }, this.getWait())\n })\n }\n\n private async execute(\n ...args: Parameters<TFn>\n ): Promise<ReturnType<TFn> | undefined> {\n if (!this.getEnabled()) return undefined\n this._abortController = new AbortController()\n try {\n this._isExecuting = true\n this._lastResult = await this.fn(...args) // EXECUTE!\n this._successCount++\n this._options.onSuccess?.(this._lastResult!, this)\n } catch (error) {\n this._errorCount++\n this._options.onError?.(error, this)\n if (this._options.throwOnError) {\n throw error\n }\n } finally {\n this._isExecuting = false\n this._isPending = false\n this._settleCount++\n this._abortController = null\n this._options.onSettled?.(this)\n }\n return this._lastResult\n }\n\n /**\n * Cancel without resetting _canLeadingExecute\n */\n private _cancel(): void {\n if (this._timeoutId) {\n clearTimeout(this._timeoutId)\n this._timeoutId = null\n }\n if (this._abortController) {\n this._abortController.abort()\n this._abortController = null\n }\n if (this._resolvePreviousPromise) {\n this._resolvePreviousPromise(this._lastResult)\n this._resolvePreviousPromise = null\n }\n this._lastArgs = undefined\n this._isPending = false\n this._isExecuting = false\n }\n\n /**\n * Cancels any pending execution or aborts any execution in progress\n */\n cancel(): void {\n this._canLeadingExecute = true\n this._cancel()\n }\n\n /**\n * Returns the last result of the debounced function\n */\n getLastResult(): ReturnType<TFn> | undefined {\n return this._lastResult\n }\n\n /**\n * Returns the number of times the function has been executed successfully\n */\n getSuccessCount(): number {\n return this._successCount\n }\n\n /**\n * Returns the number of times the function has settled (completed or errored)\n */\n getSettleCount(): number {\n return this._settleCount\n }\n\n /**\n * Returns the number of times the function has errored\n */\n getErrorCount(): number {\n return this._errorCount\n }\n\n /**\n * Returns `true` if there is a pending execution queued up for trailing execution\n */\n getIsPending(): boolean {\n return this.getEnabled() && this._isPending\n }\n\n /**\n * Returns `true` if there is currently an execution in progress\n */\n getIsExecuting(): boolean {\n return this._isExecuting\n }\n}\n\n/**\n * Creates an async debounced function that delays execution until after a specified wait time.\n * The debounced function will only execute once the wait period has elapsed without any new calls.\n * If called again during the wait period, the timer resets and a new wait period begins.\n *\n * Unlike the non-async Debouncer, this async version supports returning values from the debounced function,\n * making it ideal for API calls and other async operations where you want the result of the `maybeExecute` call\n * instead of setting the result on a state variable from within the debounced function.\n *\n * Error Handling:\n * - If an `onError` handler is provided, it will be called with the error and debouncer instance\n * - If `throwOnError` is true (default when no onError handler is provided), the error will be thrown\n * - If `throwOnError` is false (default when onError handler is provided), the error will be swallowed\n * - The error state can be checked using the underlying AsyncDebouncer instance\n * - Both onError and throwOnError can be used together - the handler will be called before any error is thrown\n *\n * @example\n * ```ts\n * const debounced = asyncDebounce(async (value: string) => {\n * const result = await saveToAPI(value);\n * return result; // Return value is preserved\n * }, {\n * wait: 1000,\n * onError: (error) => {\n * console.error('API call failed:', error);\n * },\n * throwOnError: true // Will both log the error and throw it\n * });\n *\n * // Will only execute once, 1 second after the last call\n * // Returns the API response directly\n * const result = await debounced(\"third\");\n * ```\n */\nexport function asyncDebounce<TFn extends AnyAsyncFunction>(\n fn: TFn,\n initialOptions: AsyncDebouncerOptions<TFn>,\n) {\n const asyncDebouncer = new AsyncDebouncer(fn, initialOptions)\n return asyncDebouncer.maybeExecute.bind(asyncDebouncer)\n}\n"],"names":["parseFunctionOrValue"],"mappings":"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"}
1
+ {"version":3,"file":"async-debouncer.cjs","sources":["../../src/async-debouncer.ts"],"sourcesContent":["import { Store } from '@tanstack/store'\nimport { parseFunctionOrValue } from './utils'\nimport type { AnyAsyncFunction, OptionalKeys } from './types'\n\nexport interface AsyncDebouncerState<TFn extends AnyAsyncFunction> {\n /**\n * Whether the debouncer can execute on the leading edge of the timeout\n */\n canLeadingExecute: boolean\n /**\n * Number of function executions that have resulted in errors\n */\n errorCount: number\n /**\n * Whether the debounced function is currently executing asynchronously\n */\n isExecuting: boolean\n /**\n * Whether the debouncer is waiting for the timeout to trigger execution\n */\n isPending: boolean\n /**\n * The arguments from the most recent call to maybeExecute\n */\n lastArgs: Parameters<TFn> | undefined\n /**\n * The result from the most recent successful function execution\n */\n lastResult: ReturnType<TFn> | undefined\n /**\n * Number of function executions that have completed (either successfully or with errors)\n */\n settleCount: number\n /**\n * Current execution status - 'idle' when not active, 'pending' when waiting, 'executing' when running, 'settled' when completed\n */\n status: 'disabled' | 'idle' | 'pending' | 'executing' | 'settled'\n /**\n * Number of function executions that have completed successfully\n */\n successCount: number\n}\n\nfunction getDefaultAsyncDebouncerState<\n TFn extends AnyAsyncFunction,\n>(): AsyncDebouncerState<TFn> {\n return structuredClone({\n canLeadingExecute: true,\n errorCount: 0,\n isExecuting: false,\n isPending: false,\n lastArgs: undefined,\n lastResult: undefined,\n settleCount: 0,\n successCount: 0,\n status: 'idle',\n })\n}\n\n/**\n * Options for configuring an async debounced function\n */\nexport interface AsyncDebouncerOptions<TFn extends AnyAsyncFunction> {\n /**\n * Whether the debouncer is enabled. When disabled, maybeExecute will not trigger any executions.\n * Can be a boolean or a function that returns a boolean.\n * Defaults to true.\n */\n enabled?: boolean | ((debouncer: AsyncDebouncer<TFn>) => boolean)\n /**\n * Initial state for the async debouncer\n */\n initialState?: Partial<AsyncDebouncerState<TFn>>\n /**\n * Whether to execute on the leading edge of the timeout.\n * Defaults to false.\n */\n leading?: boolean\n /**\n * Optional error handler for when the debounced function throws.\n * If provided, the handler will be called with the error and debouncer instance.\n * This can be used alongside throwOnError - the handler will be called before any error is thrown.\n */\n onError?: (error: unknown, debouncer: AsyncDebouncer<TFn>) => void\n /**\n * Optional callback to call when the debounced function is executed\n */\n onSettled?: (debouncer: AsyncDebouncer<TFn>) => void\n /**\n * Optional callback to call when the debounced function is executed\n */\n onSuccess?: (result: ReturnType<TFn>, debouncer: AsyncDebouncer<TFn>) => void\n /**\n * Whether to throw errors when they occur.\n * Defaults to true if no onError handler is provided, false if an onError handler is provided.\n * Can be explicitly set to override these defaults.\n */\n throwOnError?: boolean\n /**\n * Whether to execute on the trailing edge of the timeout.\n * Defaults to true.\n */\n trailing?: boolean\n /**\n * Delay in milliseconds to wait after the last call before executing.\n * Can be a number or a function that returns a number.\n * Defaults to 0ms\n */\n wait: number | ((debouncer: AsyncDebouncer<TFn>) => number)\n}\n\ntype AsyncDebouncerOptionsWithOptionalCallbacks = OptionalKeys<\n AsyncDebouncerOptions<any>,\n 'initialState' | 'onError' | 'onSettled' | 'onSuccess'\n>\n\nconst defaultOptions: AsyncDebouncerOptionsWithOptionalCallbacks = {\n enabled: true,\n leading: false,\n trailing: true,\n wait: 0,\n}\n\n/**\n * A class that creates an async debounced function.\n *\n * Debouncing ensures that a function is only executed after a specified delay has passed since its last invocation.\n * Each new invocation resets the delay timer. This is useful for handling frequent events like window resizing\n * or input changes where you only want to execute the handler after the events have stopped occurring.\n *\n * Unlike throttling which allows execution at regular intervals, debouncing prevents any execution until\n * the function stops being called for the specified delay period.\n *\n * Unlike the non-async Debouncer, this async version supports returning values from the debounced function,\n * making it ideal for API calls and other async operations where you want the result of the `maybeExecute` call\n * instead of setting the result on a state variable from within the debounced function.\n *\n * Error Handling:\n * - If an `onError` handler is provided, it will be called with the error and debouncer instance\n * - If `throwOnError` is true (default when no onError handler is provided), the error will be thrown\n * - If `throwOnError` is false (default when onError handler is provided), the error will be swallowed\n * - Both onError and throwOnError can be used together - the handler will be called before any error is thrown\n * - The error state can be checked using the underlying store\n *\n * State Management:\n * - The debouncer uses a reactive store for state management\n * - Use `initialState` to provide initial state values when creating the async debouncer\n * - The state includes canLeadingExecute, error count, execution status, and success/settle counts\n * - State can be accessed via the `store` property and its `state` getter\n * - The store is reactive and will notify subscribers of state changes\n *\n * @example\n * ```ts\n * const asyncDebouncer = new AsyncDebouncer(async (value: string) => {\n * const results = await searchAPI(value);\n * return results; // Return value is preserved\n * }, {\n * wait: 500,\n * onError: (error) => {\n * console.error('Search failed:', error);\n * }\n * });\n *\n * // Called on each keystroke but only executes after 500ms of no typing\n * // Returns the API response directly\n * const results = await asyncDebouncer.maybeExecute(inputElement.value);\n * ```\n */\nexport class AsyncDebouncer<TFn extends AnyAsyncFunction> {\n readonly store: Store<Readonly<AsyncDebouncerState<TFn>>> = new Store<\n AsyncDebouncerState<TFn>\n >(getDefaultAsyncDebouncerState<TFn>())\n options: AsyncDebouncerOptions<TFn>\n #abortController: AbortController | null = null\n #timeoutId: NodeJS.Timeout | null = null\n #resolvePreviousPromise:\n | ((value?: ReturnType<TFn> | undefined) => void)\n | null = null\n\n constructor(\n private fn: TFn,\n initialOptions: AsyncDebouncerOptions<TFn>,\n ) {\n this.options = {\n ...defaultOptions,\n ...initialOptions,\n throwOnError: initialOptions.throwOnError ?? !initialOptions.onError,\n }\n this.#setState(this.options.initialState ?? {})\n }\n\n /**\n * Updates the async debouncer options\n */\n setOptions = (newOptions: Partial<AsyncDebouncerOptions<TFn>>): void => {\n this.options = { ...this.options, ...newOptions }\n\n // Cancel pending execution if the debouncer is disabled\n if (!this.#getEnabled()) {\n this.cancel()\n }\n }\n\n #setState = (newState: Partial<AsyncDebouncerState<TFn>>): void => {\n this.store.setState((state) => {\n const combinedState = {\n ...state,\n ...newState,\n }\n const { isPending, isExecuting, settleCount } = combinedState\n return {\n ...combinedState,\n status: !this.#getEnabled()\n ? 'disabled'\n : isPending\n ? 'pending'\n : isExecuting\n ? 'executing'\n : settleCount > 0\n ? 'settled'\n : 'idle',\n }\n })\n }\n\n /**\n * Returns the current debouncer enabled state\n */\n #getEnabled = (): boolean => {\n return !!parseFunctionOrValue(this.options.enabled, this)\n }\n\n /**\n * Returns the current debouncer wait state\n */\n #getWait = (): number => {\n return parseFunctionOrValue(this.options.wait, this)\n }\n\n /**\n * Attempts to execute the debounced function.\n * If a call is already in progress, it will be queued.\n *\n * Error Handling:\n * - If the debounced function throws and no `onError` handler is configured,\n * the error will be thrown from this method.\n * - If an `onError` handler is configured, errors will be caught and passed to the handler,\n * and this method will return undefined.\n * - The error state can be checked using `getErrorCount()` and `getIsExecuting()`.\n *\n * @returns A promise that resolves with the function's return value, or undefined if an error occurred and was handled by onError\n * @throws The error from the debounced function if no onError handler is configured\n */\n maybeExecute = async (\n ...args: Parameters<TFn>\n ): Promise<ReturnType<TFn> | undefined> => {\n if (!this.#getEnabled()) return undefined\n this.#cancelPendingExecution()\n this.#setState({ lastArgs: args })\n\n // Handle leading execution\n if (this.options.leading && this.store.state.canLeadingExecute) {\n this.#setState({ canLeadingExecute: false })\n await this.#execute(...args)\n return this.store.state.lastResult\n }\n\n // Handle trailing execution\n if (this.options.trailing && this.#getEnabled()) {\n this.#setState({ isPending: true })\n }\n\n return new Promise((resolve) => {\n this.#resolvePreviousPromise = resolve\n this.#timeoutId = setTimeout(async () => {\n // Execute trailing if enabled\n if (this.options.trailing && this.store.state.lastArgs) {\n await this.#execute(...this.store.state.lastArgs)\n }\n\n // Reset state and resolve\n this.#setState({ canLeadingExecute: true })\n this.#resolvePreviousPromise = null\n resolve(this.store.state.lastResult)\n }, this.#getWait())\n })\n }\n\n #execute = async (\n ...args: Parameters<TFn>\n ): Promise<ReturnType<TFn> | undefined> => {\n if (!this.#getEnabled()) return undefined\n this.#abortController = new AbortController()\n try {\n this.#setState({ isExecuting: true })\n const result = await this.fn(...args) // EXECUTE!\n this.#setState({\n lastResult: result,\n successCount: this.store.state.successCount + 1,\n })\n this.options.onSuccess?.(result, this)\n } catch (error) {\n this.#setState({\n errorCount: this.store.state.errorCount + 1,\n })\n this.options.onError?.(error, this)\n if (this.options.throwOnError) {\n throw error\n }\n } finally {\n this.#setState({\n isExecuting: false,\n isPending: false,\n settleCount: this.store.state.settleCount + 1,\n })\n this.#abortController = null\n this.options.onSettled?.(this)\n }\n return this.store.state.lastResult\n }\n\n /**\n * Processes the current pending execution immediately\n */\n flush = (): void => {\n if (this.store.state.isPending && this.store.state.lastArgs) {\n this.#abortExecution() // abort any current execution\n this.#clearTimeout() // clear any existing timeout\n this.#execute(...this.store.state.lastArgs)\n }\n }\n\n #clearTimeout = (): void => {\n if (this.#timeoutId) {\n clearTimeout(this.#timeoutId)\n this.#timeoutId = null\n }\n }\n\n #cancelPendingExecution = (): void => {\n this.#clearTimeout()\n if (this.#resolvePreviousPromise) {\n this.#resolvePreviousPromise(this.store.state.lastResult)\n this.#resolvePreviousPromise = null\n }\n this.#setState({\n isPending: false,\n isExecuting: false,\n lastArgs: undefined,\n })\n }\n\n #abortExecution = (): void => {\n if (this.#abortController) {\n this.#abortController.abort()\n this.#abortController = null\n }\n }\n\n /**\n * Cancels any pending execution or aborts any execution in progress\n */\n cancel = (): void => {\n this.#cancelPendingExecution()\n this.#abortExecution()\n this.#setState({ canLeadingExecute: true })\n }\n\n /**\n * Resets the debouncer state to its default values\n */\n reset = (): void => {\n this.#setState(getDefaultAsyncDebouncerState<TFn>())\n }\n}\n\n/**\n * Creates an async debounced function that delays execution until after a specified wait time.\n * The debounced function will only execute once the wait period has elapsed without any new calls.\n * If called again during the wait period, the timer resets and a new wait period begins.\n *\n * Unlike the non-async Debouncer, this async version supports returning values from the debounced function,\n * making it ideal for API calls and other async operations where you want the result of the `maybeExecute` call\n * instead of setting the result on a state variable from within the debounced function.\n *\n * Error Handling:\n * - If an `onError` handler is provided, it will be called with the error and debouncer instance\n * - If `throwOnError` is true (default when no onError handler is provided), the error will be thrown\n * - If `throwOnError` is false (default when onError handler is provided), the error will be swallowed\n * - The error state can be checked using the underlying AsyncDebouncer instance\n * - Both onError and throwOnError can be used together - the handler will be called before any error is thrown\n *\n * State Management:\n * - Uses TanStack Store for reactive state management\n * - Use `initialState` to provide initial state values when creating the async debouncer\n * - Use `onSuccess` callback to react to successful function execution and implement custom logic\n * - Use `onError` callback to react to function execution errors and implement custom error handling\n * - Use `onSettled` callback to react to function execution completion (success or error) and implement custom logic\n * - The state includes canLeadingExecute, error count, execution status, and success/settle counts\n * - State can be accessed via `asyncDebouncer.store.state` when using the class directly\n * - When using framework adapters (React/Solid), state is accessed from `asyncDebouncer.state`\n *\n * @example\n * ```ts\n * const debounced = asyncDebounce(async (value: string) => {\n * const result = await saveToAPI(value);\n * return result; // Return value is preserved\n * }, {\n * wait: 1000,\n * onError: (error) => {\n * console.error('API call failed:', error);\n * },\n * throwOnError: true // Will both log the error and throw it\n * });\n *\n * // Will only execute once, 1 second after the last call\n * // Returns the API response directly\n * const result = await debounced(\"third\");\n * ```\n */\nexport function asyncDebounce<TFn extends AnyAsyncFunction>(\n fn: TFn,\n initialOptions: AsyncDebouncerOptions<TFn>,\n) {\n const asyncDebouncer = new AsyncDebouncer(fn, initialOptions)\n return asyncDebouncer.maybeExecute\n}\n"],"names":["Store","parseFunctionOrValue"],"mappings":"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"}
@@ -1,4 +1,43 @@
1
+ import { Store } from '@tanstack/store';
1
2
  import { AnyAsyncFunction } from './types.cjs';
3
+ export interface AsyncDebouncerState<TFn extends AnyAsyncFunction> {
4
+ /**
5
+ * Whether the debouncer can execute on the leading edge of the timeout
6
+ */
7
+ canLeadingExecute: boolean;
8
+ /**
9
+ * Number of function executions that have resulted in errors
10
+ */
11
+ errorCount: number;
12
+ /**
13
+ * Whether the debounced function is currently executing asynchronously
14
+ */
15
+ isExecuting: boolean;
16
+ /**
17
+ * Whether the debouncer is waiting for the timeout to trigger execution
18
+ */
19
+ isPending: boolean;
20
+ /**
21
+ * The arguments from the most recent call to maybeExecute
22
+ */
23
+ lastArgs: Parameters<TFn> | undefined;
24
+ /**
25
+ * The result from the most recent successful function execution
26
+ */
27
+ lastResult: ReturnType<TFn> | undefined;
28
+ /**
29
+ * Number of function executions that have completed (either successfully or with errors)
30
+ */
31
+ settleCount: number;
32
+ /**
33
+ * Current execution status - 'idle' when not active, 'pending' when waiting, 'executing' when running, 'settled' when completed
34
+ */
35
+ status: 'disabled' | 'idle' | 'pending' | 'executing' | 'settled';
36
+ /**
37
+ * Number of function executions that have completed successfully
38
+ */
39
+ successCount: number;
40
+ }
2
41
  /**
3
42
  * Options for configuring an async debounced function
4
43
  */
@@ -9,6 +48,10 @@ export interface AsyncDebouncerOptions<TFn extends AnyAsyncFunction> {
9
48
  * Defaults to true.
10
49
  */
11
50
  enabled?: boolean | ((debouncer: AsyncDebouncer<TFn>) => boolean);
51
+ /**
52
+ * Initial state for the async debouncer
53
+ */
54
+ initialState?: Partial<AsyncDebouncerState<TFn>>;
12
55
  /**
13
56
  * Whether to execute on the leading edge of the timeout.
14
57
  * Defaults to false.
@@ -61,10 +104,18 @@ export interface AsyncDebouncerOptions<TFn extends AnyAsyncFunction> {
61
104
  * instead of setting the result on a state variable from within the debounced function.
62
105
  *
63
106
  * Error Handling:
64
- * - If an error occurs during execution and no `onError` handler is provided, the error will be thrown and propagate up to the caller.
65
- * - If an `onError` handler is provided, errors will be caught and passed to the handler instead of being thrown.
66
- * - The error count can be tracked using `getErrorCount()`.
67
- * - The debouncer maintains its state and can continue to be used after an error occurs.
107
+ * - If an `onError` handler is provided, it will be called with the error and debouncer instance
108
+ * - If `throwOnError` is true (default when no onError handler is provided), the error will be thrown
109
+ * - If `throwOnError` is false (default when onError handler is provided), the error will be swallowed
110
+ * - Both onError and throwOnError can be used together - the handler will be called before any error is thrown
111
+ * - The error state can be checked using the underlying store
112
+ *
113
+ * State Management:
114
+ * - The debouncer uses a reactive store for state management
115
+ * - Use `initialState` to provide initial state values when creating the async debouncer
116
+ * - The state includes canLeadingExecute, error count, execution status, and success/settle counts
117
+ * - State can be accessed via the `store` property and its `state` getter
118
+ * - The store is reactive and will notify subscribers of state changes
68
119
  *
69
120
  * @example
70
121
  * ```ts
@@ -84,36 +135,15 @@ export interface AsyncDebouncerOptions<TFn extends AnyAsyncFunction> {
84
135
  * ```
85
136
  */
86
137
  export declare class AsyncDebouncer<TFn extends AnyAsyncFunction> {
138
+ #private;
87
139
  private fn;
88
- private _options;
89
- private _abortController;
90
- private _canLeadingExecute;
91
- private _errorCount;
92
- private _isExecuting;
93
- private _isPending;
94
- private _lastArgs;
95
- private _lastResult;
96
- private _settleCount;
97
- private _successCount;
98
- private _timeoutId;
99
- private _resolvePreviousPromise;
140
+ readonly store: Store<Readonly<AsyncDebouncerState<TFn>>>;
141
+ options: AsyncDebouncerOptions<TFn>;
100
142
  constructor(fn: TFn, initialOptions: AsyncDebouncerOptions<TFn>);
101
143
  /**
102
- * Updates the debouncer options
103
- */
104
- setOptions(newOptions: Partial<AsyncDebouncerOptions<TFn>>): void;
105
- /**
106
- * Returns the current debouncer options
144
+ * Updates the async debouncer options
107
145
  */
108
- getOptions(): AsyncDebouncerOptions<TFn>;
109
- /**
110
- * Returns the current debouncer enabled state
111
- */
112
- getEnabled(): boolean;
113
- /**
114
- * Returns the current debouncer wait state
115
- */
116
- getWait(): number;
146
+ setOptions: (newOptions: Partial<AsyncDebouncerOptions<TFn>>) => void;
117
147
  /**
118
148
  * Attempts to execute the debounced function.
119
149
  * If a call is already in progress, it will be queued.
@@ -128,40 +158,19 @@ export declare class AsyncDebouncer<TFn extends AnyAsyncFunction> {
128
158
  * @returns A promise that resolves with the function's return value, or undefined if an error occurred and was handled by onError
129
159
  * @throws The error from the debounced function if no onError handler is configured
130
160
  */
131
- maybeExecute(...args: Parameters<TFn>): Promise<ReturnType<TFn> | undefined>;
132
- private execute;
161
+ maybeExecute: (...args: Parameters<TFn>) => Promise<ReturnType<TFn> | undefined>;
133
162
  /**
134
- * Cancel without resetting _canLeadingExecute
163
+ * Processes the current pending execution immediately
135
164
  */
136
- private _cancel;
165
+ flush: () => void;
137
166
  /**
138
167
  * Cancels any pending execution or aborts any execution in progress
139
168
  */
140
- cancel(): void;
169
+ cancel: () => void;
141
170
  /**
142
- * Returns the last result of the debounced function
171
+ * Resets the debouncer state to its default values
143
172
  */
144
- getLastResult(): ReturnType<TFn> | undefined;
145
- /**
146
- * Returns the number of times the function has been executed successfully
147
- */
148
- getSuccessCount(): number;
149
- /**
150
- * Returns the number of times the function has settled (completed or errored)
151
- */
152
- getSettleCount(): number;
153
- /**
154
- * Returns the number of times the function has errored
155
- */
156
- getErrorCount(): number;
157
- /**
158
- * Returns `true` if there is a pending execution queued up for trailing execution
159
- */
160
- getIsPending(): boolean;
161
- /**
162
- * Returns `true` if there is currently an execution in progress
163
- */
164
- getIsExecuting(): boolean;
173
+ reset: () => void;
165
174
  }
166
175
  /**
167
176
  * Creates an async debounced function that delays execution until after a specified wait time.
@@ -179,6 +188,16 @@ export declare class AsyncDebouncer<TFn extends AnyAsyncFunction> {
179
188
  * - The error state can be checked using the underlying AsyncDebouncer instance
180
189
  * - Both onError and throwOnError can be used together - the handler will be called before any error is thrown
181
190
  *
191
+ * State Management:
192
+ * - Uses TanStack Store for reactive state management
193
+ * - Use `initialState` to provide initial state values when creating the async debouncer
194
+ * - Use `onSuccess` callback to react to successful function execution and implement custom logic
195
+ * - Use `onError` callback to react to function execution errors and implement custom error handling
196
+ * - Use `onSettled` callback to react to function execution completion (success or error) and implement custom logic
197
+ * - The state includes canLeadingExecute, error count, execution status, and success/settle counts
198
+ * - State can be accessed via `asyncDebouncer.store.state` when using the class directly
199
+ * - When using framework adapters (React/Solid), state is accessed from `asyncDebouncer.state`
200
+ *
182
201
  * @example
183
202
  * ```ts
184
203
  * const debounced = asyncDebounce(async (value: string) => {