Debounce promise-returning & async functions
npm install p-debounce
import pDebounce from 'p-debounce';
const expensiveCall = async input => input;
const debouncedFunction = pDebounce(expensiveCall, 200);
for (const number of [1, 2, 3]) {
(async () => {
console.log(await debouncedFunction(number));
})();
}
//=> 3
//=> 3
//=> 3
Returns a function that delays calling fn until after wait milliseconds have elapsed since the last time it was called.
Type: Function
Promise-returning/async function to debounce.
Type: number
Milliseconds to wait before calling fn. Must be at most 2147483647 (about 24.8 days).
Type: object
Type: boolean
Default: false
Call the fn on the leading edge of the timeout. Meaning immediately, instead of waiting for wait milliseconds.
The other calls in the same window resolve with the result of the leading call, so they settle only when the leading call settles.
[!NOTE]
fnmust not wait for the promise of a call it makes to its own debounced function. That call waits for the leading call, so it never settles.
Type: AbortSignal
An AbortSignal to cancel the debounced function.
Execute function_ unless a previous call is still pending, in which case, return the pending promise. Useful, for example, to avoid processing extra button clicks if the previous one is not complete.
[!NOTE]
function_must not wait for the promise of a call it makes to its own debounced function. That call returns the pending promise (or is queued behind the running call withafter: true), so it never settles.
import {setTimeout as delay} from 'timers/promises';
import pDebounce from 'p-debounce';
const expensiveCall = async value => {
await delay(200);
return value;
};
const debouncedFunction = pDebounce.promise(expensiveCall);
for (const number of [1, 2, 3]) {
(async () => {
console.log(await debouncedFunction(number));
})();
}
//=> 1
//=> 1
//=> 1
Type: Function
Promise-returning/async function to debounce.
Type: object
Type: boolean
Default: false
If a call is made while a previous call is still running, queue the latest arguments and run the function again after the current execution completes.
Each caller settles as soon as the execution it is waiting for completes. Calls queued during the same execution share the result of the latest one.
Use cases:
after: false (default): API fetches, data loading, read operations - concurrent calls share the same result.after: true: Saving data, file writes, state updates - ensures latest data is never lost.import {setTimeout as delay} from 'timers/promises';
import pDebounce from 'p-debounce';
const save = async data => {
await delay(200);
console.log(`Saved: ${data}`);
return data;
};
const debouncedSave = pDebounce.promise(save, {after: true});
// If data changes while saving, it will save again with the latest data
debouncedSave('data1');
debouncedSave('data2'); // This will run after the first save completes
//=> Saved: data1
//=> Saved: data2
For autosave, combine trailing debounce with serialization to prevent concurrent saves from racing:
import pDebounce from 'p-debounce';
const saveDocument = async content => {
await fetch('/api/save', {
method: 'POST',
body: JSON.stringify({content}),
});
};
// Serialize saves, then debounce keystrokes
const serializedSave = pDebounce.promise(saveDocument, {after: true});
const autosave = pDebounce(serializedSave, 1000);
textArea.addEventListener('input', () => {
autosave(textArea.value);
});
// 1. Waits 1s after typing stops
// 2. If user types during save, queues next save with latest content
// 3. Prevents race conditions where slow saves overwrite newer data