Debounced Async Function with Abort Controller
Here's a TypeScript implementation that debounces async functions and properly cancels stale calls:
type AsyncFunction<T extends any[], R> = (...args: T) => Promise<R>;
function debounceAsync<T extends any[], R>(
fn: AsyncFunction<T, R>,
delayMs: number
): AsyncFunction<T, R> {
let timeoutId: NodeJS.Timeout | null = null;
let currentController: AbortController | null = null;
return async (...args: T): Promise<R> => {
// Cancel previous timeout
if (timeoutId) {
clearTimeout(timeoutId);
}
// Abort previous pending call
if (currentController) {
currentController.abort();
}
// Create new abort controller for this call
currentController = new AbortController();
const thisController = currentController;
return new Promise<R>((resolve, reject) => {
timeoutId = setTimeout(async () => {
try {
// Check if this call was aborted before execution
if (thisController.signal.aborted) {
reject(new Error('Aborted'));
return;
}
const result = await fn(...args);
// Only resolve if not aborted during execution
if (!thisController.signal.aborted) {
resolve(result);
}
} catch (error) {
if (!thisController.signal.aborted) {
reject(error);
}
}
}, delayMs);
});
};
}Usage Example
// Simulated API call
async function searchUsers(query: string): Promise<string[]> {
await new Promise(resolve => setTimeout(resolve, 500));
return [`User matching "${query}"`];
}
// Create debounced version
const debouncedSearch = debounceAsync(searchUsers, 300);
// Usage in practice
async function handleSearchInput(query: string) {
try {
// Only the last call within 300ms window will complete
const results = await debouncedSearch(query);
console.log('Results:', results);
} catch (error) {
// Stale calls will be rejected with 'Aborted' error
console.log('Search cancelled');
}
}
// Rapid calls - only last one resolves
handleSearchInput('j'); // Cancelled
handleSearchInput('jo'); // Cancelled
handleSearchInput('john'); // Executes after 300msKey Features
- Debouncing: Delays execution until calls stop for
delayMs - Abort Control: Cancels stale promises using AbortController
- Type Safety: Full TypeScript generics support
- Latest Win: Only the most recent call within the window completes
This is ideal for search inputs, autocomplete, or any scenario where you want to wait for user input to stabilize before making expensive async operations.

