
Product
Rust Support Now in Beta
Socket's Rust support is moving to Beta: all users can scan Cargo projects and generate SBOMs, including Cargo.toml-only crates, with Rust-aware supply chain checks.
@danielhaim/modulator
Advanced tools
An advanced debouncing utility designed to optimize high-frequency events in web applications, such as scroll, resize, and input.
Modulator is an advanced debouncing utility, now written in TypeScript, designed to optimize high-frequency events in web applications (e.g., scroll, resize, input). This standalone solution offers enhanced performance and flexibility compared to basic debouncing functions.
Key features include:
maxCacheSize
.immediate: true
) to trigger the function on the leading edge.maxWait
parameter to guarantee execution after a certain period, even with continuous calls..cancel()
method to abort pending debounced calls and reject their associated Promise.npm install @danielhaim/modulator
# or
yarn add @danielhaim/modulator
import { modulate } from '@danielhaim/modulator';
// or import default Modulator from '@danielhaim/modulator'; // If using the object wrapper (less common now)
async function myAsyncFunction(query) {
console.log('Executing with:', query);
// Simulate work
await new Promise(res => setTimeout(res, 50));
if (query === 'fail') throw new Error('Failed!');
return `Result for ${query}`;
}
const debouncedFunc = modulate(myAsyncFunction, 300);
debouncedFunc('query1')
.then(result => console.log('Success:', result)) // Logs 'Success: Result for query1' after 300ms
.catch(error => console.error('Caught:', error));
debouncedFunc('fail')
.then(result => console.log('Success:', result))
.catch(error => console.error('Caught:', error)); // Logs 'Caught: Error: Failed!' after 300ms
// Using async/await
async function run() {
try {
const result = await debouncedFunc('query2');
console.log('Async Success:', result);
} catch (error) {
console.error('Async Error:', error);
}
}
run();
const { modulate } = require('@danielhaim/modulator');
const debouncedFunc = modulate(/* ... */);
// ... usage is the same
Include the UMD build:
<!-- Download dist/modulator.umd.js or use a CDN like jsDelivr/unpkg -->
<script src="path/to/modulator.umd.js"></script>
<script>
// Modulator is available globally
const debouncedFunc = Modulator.modulate(myFunction, 200);
myButton.addEventListener('click', async () => {
try {
const result = await debouncedFunc('data');
console.log('Got:', result);
} catch (e) {
console.error('Error:', e);
}
});
</script>
requirejs(['path/to/modulator.amd'], function(Modulator) {
const debouncedFunc = Modulator.modulate(myFunction, 200);
// ...
});
modulate(func, wait, immediate?, context?, maxCacheSize?, maxWait?)
Creates a debounced function that delays invoking func
until after wait
milliseconds have elapsed since the last time the debounced function was invoked.
Returns: DebouncedFunction
- A new function that returns a Promise
. This promise resolves with the return value of the original func
or rejects if func
throws an error, returns a rejected promise, or if the debounced call is cancelled via .cancel()
.
Name | Type | Attributes | Default | Description |
---|---|---|---|---|
func | Function | The function to debounce. Can be synchronous or asynchronous (return a Promise). | ||
wait | number | The debouncing wait time in milliseconds. Must be non-negative. | ||
immediate? | boolean | <optional> | false | If true , triggers func on the leading edge instead of the trailing edge. Subsequent calls within the wait period are ignored until the cooldown finishes. |
context? | object | <optional> | null | The context (this ) to apply when invoking func . Defaults to the context the debounced function is called with. |
maxCacheSize? | number | <optional> | 100 | The maximum number of results to cache based on arguments. Uses JSON.stringify for keys. Set to 0 to disable caching. Must be non-negative. |
maxWait? | number | null | <optional> | null | The maximum time (in ms) func is allowed to be delayed before it's invoked, even if calls keep occurring. Must be >= wait if set. |
The returned debounced function has an additional method:
debouncedFunc.cancel()
: Cancels any pending invocation of the debounced function. If a call was pending, the Promise
returned by that call will be rejected with an error indicating cancellation. This does not clear the result cache.maxCacheSize > 0
, Modulator caches the results (resolved values) of successful func
invocations.JSON.stringify(arguments)
. This works well for primitive arguments but may have limitations with complex objects, functions, or circular references.func
is not invoked.maxCacheSize
, the oldest entry is removed. Accessing a cached item marks it as recently used.function handleInput(value) {
console.log('Processing input:', value);
// e.g., make API call
}
// Debounce to run only 500ms after the user stops typing
const debouncedHandleInput = modulate(handleInput, 500);
searchInput.addEventListener('input', (event) => {
debouncedHandleInput(event.target.value)
.catch(err => console.error("Input Error:", err)); // Optional: Catch potential errors
});
function handleClick() {
console.log('Button clicked!');
// Perform action immediately, but prevent rapid re-clicks
}
// Trigger immediately, then ignore calls for 1000ms
const debouncedClick = modulate(handleClick, 1000, true);
myButton.addEventListener('click', () => {
debouncedClick().catch(err => {
// Only log if it's not a cancellation error, as we don't cancel here
if (err.message !== 'Debounced function call was cancelled.') {
console.error("Click Error:", err);
}
});
});
async function searchAPI(query) {
if (!query) return []; // Handle empty query
console.log(`Searching API for: ${query}`);
const response = await fetch(`/api/search?q=${query}`);
if (!response.ok) throw new Error(`API Error: ${response.statusText}`);
return response.json();
}
const debouncedSearch = modulate(searchAPI, 400);
const statusElement = document.getElementById('search-status'); // Assume element exists
const searchInput = document.getElementById('search-input'); // Assume element exists
searchInput.addEventListener('input', async (event) => {
const query = event.target.value;
statusElement.textContent = 'Searching...';
try {
// debouncedSearch returns a promise here
const results = await debouncedSearch(query);
// Check if query is still relevant before updating UI
if (query === searchInput.value) {
statusElement.textContent = `Found ${results.length} results.`;
// Update UI with results
} else {
console.log("Query changed, ignoring results for:", query);
}
} catch (error) {
// Handle errors from searchAPI OR cancellation errors
if (error.message === 'Debounced function call was cancelled.') {
console.log('Search cancelled.');
// Status might already be 'Searching...' which is fine
} else {
console.error('Search failed:', error);
statusElement.textContent = `Error: ${error.message}`;
}
}
});
// Example of cancellation (Alternative approach combining input/cancel)
let currentQuery = '';
searchInput.addEventListener('input', async (event) => {
const query = event.target.value;
currentQuery = query;
statusElement.textContent = 'Typing...';
// Cancel any previous pending search before starting a new one
debouncedSearch.cancel(); // Cancel previous timer/promise
if (!query) { // Handle empty input immediately
statusElement.textContent = 'Enter search term.';
// Clear results UI
return;
}
// Only proceed if query is not empty after debounce period
try {
statusElement.textContent = 'Waiting...'; // Indicate waiting for debounce
// Start new search (will wait 400ms unless cancelled again)
const results = await debouncedSearch(query); // New promise for this call
// Re-check if the query changed *after* the await completed
if (query === currentQuery) {
statusElement.textContent = `Found ${results.length} results.`;
// Update UI
} else {
console.log('Results ignored, query changed.');
// Status might remain 'Typing...' from next input event
}
} catch (error) {
// Handle errors from the awaited promise
if (error.message !== 'Debounced function call was cancelled.') {
console.error('Search failed:', error);
statusElement.textContent = `Error: ${error.message}`;
} else {
// Ignore cancellation errors here as we trigger cancel often
console.log('Search promise cancelled.');
}
}
});
maxWait
function saveData() {
console.log('Saving data to server...');
// API call to save
return Promise.resolve({ status: 'Saved' }); // Example return
}
// Debounce saving by 1 second, but ensure it saves
// at least once every 5 seconds even if user keeps typing.
const debouncedSave = modulate(saveData, 1000, false, null, 0, 5000); // No cache, maxWait 5s
const saveStatus = document.getElementById('save-status'); // Assume element exists
const textArea = document.getElementById('my-textarea'); // Assume element exists
textArea.addEventListener('input', () => {
saveStatus.textContent = 'Changes detected, waiting to save...';
debouncedSave()
.then(result => {
// Check if still relevant (optional)
saveStatus.textContent = `Saved successfully at ${new Date().toLocaleTimeString()}`;
console.log('Save result:', result);
})
.catch(err => {
if (err.message !== 'Debounced function call was cancelled.') {
console.error("Save Error:", err);
saveStatus.textContent = `Save failed: ${err.message}`;
} else {
console.log("Save cancelled.");
// Status remains 'waiting...' or might be updated by next input
}
});
});
If you encounter any bugs while using Modulator, please report them to the GitHub issue tracker. When submitting a bug report, please include as much information as possible, such as:
FAQs
An advanced debouncing utility designed to optimize high-frequency events in web applications, such as scroll, resize, and input.
We found that @danielhaim/modulator demonstrated a healthy version release cadence and project activity because the last version was released less than a year ago. It has 1 open source maintainer collaborating on the project.
Did you know?
Socket for GitHub automatically highlights issues in each pull request and monitors the health of all your open source dependencies. Discover the contents of your packages and block harmful activity before you install or update your dependencies.
Product
Socket's Rust support is moving to Beta: all users can scan Cargo projects and generate SBOMs, including Cargo.toml-only crates, with Rust-aware supply chain checks.
Product
Socket Fix 2.0 brings targeted CVE remediation, smarter upgrade planning, and broader ecosystem support to help developers get to zero alerts.
Security News
Socket CEO Feross Aboukhadijeh joins Risky Business Weekly to unpack recent npm phishing attacks, their limited impact, and the risks if attackers get smarter.