Skip to content

Repository files navigation

Modulator

npm versionDownloadsGitHubBuild DistributionDeploy DocsTypeScript definitions

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:

  • Promise-based Return: Always returns a Promise that resolves with the result of your function or rejects on error/cancellation.
  • Configurable Caching: Optional result caching based on arguments with controllable maxCacheSize.
  • Immediate Execution: Option (immediate: true) to trigger the function on the leading edge.
  • Maximum Wait Time: Optional maxWait parameter to guarantee execution after a certain period, even with continuous calls.
  • Cancellation: A .cancel() method to abort pending debounced calls and reject their associated Promise.
  • TypeScript Support: Ships with built-in type definitions for a better developer experience.

Demo

Modulator Demo

API Documentation

Installation

npm install @danielhaim/modulator
# or
yarn add @danielhaim/modulator

Usage

ES Modules (Recommended)

import{modulate}from'@danielhaim/modulator';// or import default Modulator from '@danielhaim/modulator'; // If using the object wrapper (less common now)asyncfunctionmyAsyncFunction(query){console.log('Executing with:',query);// Simulate workawaitnewPromise(res=>setTimeout(res,50));if(query==='fail')thrownewError('Failed!');return`Result for ${query}`;}constdebouncedFunc=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/awaitasyncfunctionrun(){try{constresult=awaitdebouncedFunc('query2');console.log('Async Success:',result);}catch(error){console.error('Async Error:',error);}}run();

CommonJS

const{ modulate }=require('@danielhaim/modulator');constdebouncedFunc=modulate(/* ... */);// ... usage is the same

Browser (UMD / Direct Script)

Include the UMD build:

<!-- Download dist/modulator.umd.js or use a CDN like jsDelivr/unpkg --><scriptsrc="path/to/modulator.umd.js"></script><script>// Modulator is available globallyconstdebouncedFunc=Modulator.modulate(myFunction,200);myButton.addEventListener('click',async()=>{try{constresult=awaitdebouncedFunc('data');console.log('Got:',result);}catch(e){console.error('Error:',e);}});</script>

AMD

requirejs(['path/to/modulator.amd'],function(Modulator){constdebouncedFunc=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().

Parameters

NameTypeAttributesDefaultDescription
funcFunctionThe function to debounce. Can be synchronous or asynchronous (return a Promise).
waitnumberThe debouncing wait time in milliseconds. Must be non-negative.
immediate?boolean<optional>falseIf 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>nullThe context (this) to apply when invoking func. Defaults to the context the debounced function is called with.
maxCacheSize?number<optional>100The 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>nullThe maximum time (in ms) func is allowed to be delayed before it's invoked, even if calls keep occurring. Must be >= wait if set.

Enhanced Functionality

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.

Caching

  • When maxCacheSize > 0, Modulator caches the results (resolved values) of successful func invocations.
  • The cache key is generated using JSON.stringify(arguments). This works well for primitive arguments but may have limitations with complex objects, functions, or circular references.
  • If a subsequent call is made with the same arguments (generating the same cache key) while the result is in the cache, the cached result is returned immediately via a resolved Promise, and func is not invoked.
  • The cache uses a simple Least Recently Used (LRU) eviction strategy: when the cache exceeds maxCacheSize, the oldest entry is removed. Accessing a cached item marks it as recently used.

Examples

Basic Debounce (Trailing Edge)

functionhandleInput(value){console.log('Processing input:',value);// e.g., make API call}// Debounce to run only 500ms after the user stops typingconstdebouncedHandleInput=modulate(handleInput,500);searchInput.addEventListener('input',(event)=>{debouncedHandleInput(event.target.value).catch(err=>console.error("Input Error:",err));// Optional: Catch potential errors});

Immediate Execution (Leading Edge)

functionhandleClick(){console.log('Button clicked!');// Perform action immediately, but prevent rapid re-clicks}// Trigger immediately, then ignore calls for 1000msconstdebouncedClick=modulate(handleClick,1000,true);myButton.addEventListener('click',()=>{debouncedClick().catch(err=>{// Only log if it's not a cancellation error, as we don't cancel hereif(err.message!=='Debounced function call was cancelled.'){console.error("Click Error:",err);}});});

Handling Promise Results & Errors

asyncfunctionsearchAPI(query){if(!query)return[];// Handle empty queryconsole.log(`Searching API for: ${query}`);constresponse=awaitfetch(`/api/search?q=${query}`);if(!response.ok)thrownewError(`API Error: ${response.statusText}`);returnresponse.json();}constdebouncedSearch=modulate(searchAPI,400);conststatusElement=document.getElementById('search-status');// Assume element existsconstsearchInput=document.getElementById('search-input');// Assume element existssearchInput.addEventListener('input',async(event)=>{constquery=event.target.value;statusElement.textContent='Searching...';try{// debouncedSearch returns a promise hereconstresults=awaitdebouncedSearch(query);// Check if query is still relevant before updating UIif(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 errorsif(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)letcurrentQuery='';searchInput.addEventListener('input',async(event)=>{constquery=event.target.value;currentQuery=query;statusElement.textContent='Typing...';// Cancel any previous pending search before starting a new onedebouncedSearch.cancel();// Cancel previous timer/promiseif(!query){// Handle empty input immediatelystatusElement.textContent='Enter search term.';// Clear results UIreturn;}// Only proceed if query is not empty after debounce periodtry{statusElement.textContent='Waiting...';// Indicate waiting for debounce// Start new search (will wait 400ms unless cancelled again)constresults=awaitdebouncedSearch(query);// New promise for this call// Re-check if the query changed *after* the await completedif(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 promiseif(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 oftenconsole.log('Search promise cancelled.');}}});

Using maxWait

functionsaveData(){console.log('Saving data to server...');// API call to savereturnPromise.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.constdebouncedSave=modulate(saveData,1000,false,null,0,5000);// No cache, maxWait 5sconstsaveStatus=document.getElementById('save-status');// Assume element existsconsttextArea=document.getElementById('my-textarea');// Assume element existstextArea.addEventListener('input',()=>{saveStatus.textContent='Changes detected, waiting to save...';debouncedSave().then(result=>{// Check if still relevant (optional)saveStatus.textContent=`Saved successfully at ${newDate().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}});});

Resources

Report Bugs

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:

  • Version of Modulator used.
  • Browser/Node.js environment and version.
  • Steps to reproduce the bug.
  • Expected behavior vs. actual behavior.
  • Any relevant code snippets.

About

An advanced debouncing utility designed to optimize high-frequency events in web applications, such as scroll, resize, and input.

Topics

Resources

Stars

2 stars

Watchers

1 watching

Forks

Releases

Packages

Used by

Contributors

Languages