Skip to content

Commit 3e1c6c6

Browse files
committed
feat(utils): add cancel support to debounce and debounceSync
- Added `.cancel()` method to both `debounce` and `debounceSync` to allow cancelling scheduled executions - Updated README examples and API table to reflect the new behavior and document usage - Exposed a `dispose()` method on the EventEmitter to clean up internal timers (e.g. debounced warnings)
1 parent a8a8a13 commit 3e1c6c6

File tree

3 files changed

+113
-20
lines changed

3 files changed

+113
-20
lines changed

examples/utils.md

Lines changed: 57 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -11,14 +11,16 @@ const utils = atomix.utils;
1111

1212
---
1313
## The APIs
14-
| API | Description |
15-
| ---------------------------------- | ----------------------------------------------------------------- |
16-
| [generateRandom](#-generaterandom) | Generate customizable random strings with optional character sets |
17-
| [sleep](#-sleep) | Pause execution for a specified time using a Promise |
18-
| [debounce](#-debounce) | Delay function execution until no calls occur for a set time |
19-
| [throttle](#-throttle) | Limit function calls to at most once per specified delay |
20-
| [once](#-once) | Ensure a function can only be executed a single time |
21-
| [noop](#-noop) | A function that performs no operation (no-op) |
14+
| API | Description |
15+
| ---------------------------------- | -------------------------------------------------------------------- |
16+
| [generateRandom](#-generaterandom) | Generate customizable random strings with optional character sets |
17+
| [sleep](#-sleep) | Pause execution for a specified time using a Promise |
18+
| [debounce](#-debounce) | Delay async function execution until no calls occur for a set time |
19+
| [debounceSync](#-debouncesync) | Delay synchronous function calls until no calls occur for a set time |
20+
| [throttle](#-throttle) | Limit function calls to at most once per specified delay |
21+
| [once](#-once) | Ensure a function can only be executed a single time |
22+
| [noop](#-noop) | A function that performs no operation (no-op) |
23+
2224

2325

2426
---
@@ -54,18 +56,63 @@ await utils.sleep(1500);
5456
### 🌀 `debounce`
5557
Signature: `debounce(fn, delay)`
5658

57-
Delay execution until no calls have occurred within the specified delay.
59+
Delays execution of `fn` until no calls have occurred within the specified delay. Returns a debounced function that returns a Promise resolving to the result of `fn`.
60+
61+
**Usage Example:**
62+
```ts
63+
const debouncedLog = utils.debounce((msg: string) => {
64+
console.log('Debounced:', msg);
65+
return msg.length;
66+
}, 1000);
67+
68+
debouncedLog('Hello');
69+
debouncedLog('World').then(length => {
70+
console.log(`Last message length: ${length}`);
71+
});
72+
```
73+
74+
**Canceling a Pending Call:**
5875

76+
Delays execution of a synchronous function `fn` until no calls have occurred within the specified delay. Returns a debounced function with a `.cancel()` method.
77+
78+
**Usage Example:**
5979
```ts
6080
const debouncedLog = utils.debounce((msg) => {
6181
console.log('Debounced:', msg);
6282
}, 1000);
6383

6484
debouncedLog('Hello');
65-
debouncedLog('World');
85+
debouncedLog.cancel(); // Cancel the pending call; promise from last call rejects
86+
```
87+
88+
### 🌀 `debounceSync`
89+
Signature: `debounceSync(fn, delay)`
90+
91+
Delays execution of `fn` until no calls have occurred within the specified delay. Returns a debounced function that returns a Promise resolving to the result of `fn`.
92+
93+
```ts
94+
const debouncedLog = utils.debounceSync((msg: string) => {
95+
console.log('Debounced:', msg);
96+
}, 1000);
97+
98+
debouncedLog('Hello');
99+
debouncedLog('World');
66100
// Only logs "Debounced: World" after 1 second
67101
```
68102

103+
**Canceling a Pending Call:**
104+
105+
The returned debounced function includes a `.cancel()` method to cancel any pending invocation before it happens.
106+
107+
```ts
108+
const debouncedLog = utils.debounceSync((msg) => {
109+
console.log('Debounced:', msg);
110+
}, 1000);
111+
112+
debouncedLog('Hello');
113+
debouncedLog.cancel(); // Cancel the pending call silently, no log will happen
114+
```
115+
69116
### 🚦 `throttle`
70117
Signature: `throttle(fn, delay)`
71118

src/domains/utils/utils.ts

Lines changed: 37 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -70,24 +70,29 @@ class CommonUtils {
7070
}
7171

7272
/**
73-
* Returns a debounced version of the given function.
73+
* Debounces a function, delaying its invocation until the specified delay
74+
* period has passed since the last call.
7475
*
75-
* When the given function is called, it will wait the specified delay before
76-
* actually calling the function. If the function is called multiple times
77-
* within that delay, it will only call the function once after the delay
78-
* has passed.
76+
* The debounced function returns a promise that resolves with the result
77+
* of the original function. If the debounced function is called again
78+
* before the delay period has passed, the previous promise is rejected
79+
* with an error and a new promise is returned.
80+
*
81+
* The debounced function also has a `cancel` method that can be called to
82+
* cancel the current pending invocation. If called, the promise is rejected
83+
* with an error and the timer is cleared.
7984
*
8085
* @param fn - The function to debounce.
81-
* @param delay - The number of milliseconds to delay calling the function.
82-
* @returns A debounced version of the given function that returns a promise.
86+
* @param delay - The number of milliseconds to delay the invocation of the function.
87+
* @returns A debounced version of the given function.
8388
* @since v1.0.0
8489
*/
8590
debounce<T extends (...args: any[]) => any>(fn: T, delay: number): (...args: Parameters<T>) => Promise<ReturnType<T>> {
8691
let timeout: NodeJS.Timeout | null = null;
8792
let resolveFn: ((value: any) => void) | null = null;
8893
let rejectFn: ((reason?: any) => void) | null = null;
8994

90-
return (...args: Parameters<T>) => {
95+
const debounced = (...args: Parameters<T>) => {
9196
if (timeout) clearTimeout(timeout);
9297

9398
return new Promise<ReturnType<T>>((resolve, reject) => {
@@ -103,6 +108,19 @@ class CommonUtils {
103108
}, delay);
104109
});
105110
};
111+
112+
debounced.cancel = () => {
113+
if (timeout) {
114+
clearTimeout(timeout);
115+
timeout = null;
116+
// Reject the pending Promise if any
117+
rejectFn?.(new Error('Debounced function cancelled'));
118+
resolveFn = null;
119+
rejectFn = null;
120+
}
121+
};
122+
123+
return debounced;
106124
}
107125

108126
/**
@@ -128,7 +146,7 @@ class CommonUtils {
128146
): (...args: Parameters<T>) => void {
129147
let timeout: ReturnType<typeof setTimeout> | null = null;
130148

131-
return (...args: Parameters<T>) => {
149+
const debounced = (...args: Parameters<T>) => {
132150
if (timeout) clearTimeout(timeout);
133151

134152
timeout = setTimeout(() => {
@@ -139,7 +157,16 @@ class CommonUtils {
139157
options?.onError?.(error);
140158
}
141159
}, delay);
142-
};
160+
}
161+
162+
debounced.cancel = () => {
163+
if (timeout) {
164+
clearTimeout(timeout);
165+
timeout = null;
166+
}
167+
}
168+
169+
return debounced;
143170
}
144171

145172
/**

src/tools/events/EventEmitter.ts

Lines changed: 19 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -298,6 +298,25 @@ export class EventEmitter {
298298
return this
299299
}
300300

301+
/**
302+
* Cleans up any internal resources used by this instance.
303+
*
304+
* Specifically, cancels any pending debounced handlers (such as
305+
* warnings about maximum handlers) to prevent timers from
306+
* keeping the process alive or causing side effects after disposal.
307+
*
308+
* This method should be called when the instance is no longer needed,
309+
* such as during test teardown or object cleanup.
310+
*
311+
* @since v1.0.15
312+
*/
313+
dispose() {
314+
const { builtInHandler, customHandler } = this.#_helpers.onMaxHandlers;
315+
(builtInHandler as any)?.cancel();
316+
if (typeof customHandler !== 'function') { return }
317+
(customHandler as any)?.cancel();
318+
}
319+
301320
/**
302321
* Retrieves the maximum number of event handlers allowed for this EventEmitter instance.
303322
*

0 commit comments

Comments
 (0)