Performance Utilities API Reference
Performance utilities for optimizing function execution with debouncing and throttling.
Table of Contents
Overview
Bindra provides performance optimization utilities:
- debounce() - Delay function execution until after a pause
- throttle() - Limit function execution rate
- debounceWithMaxWait() - Debounce with guaranteed maximum wait
These utilities are essential for:
- Search input handling
- Scroll event optimization
- Resize event handling
- Auto-save functionality
- API rate limiting
import { debounce, throttle, debounceWithMaxWait } from 'bindra';Functions
debounce()
Creates a debounced function that delays execution until after a specified delay since the last invocation.
function debounce<T extends (...args: any[]) => any>(
fn: T,
delay: number
): (...args: Parameters<T>) => voidParameters
- fn (
Function) - The function to debounce - delay (
number) - Delay in milliseconds
Returns
Debounced version of the function
How It Works
The function only executes after delay milliseconds of inactivity. Each new call resets the timer.
Call 1 ─┬─── (timer starts)
Call 2 ─┴─┬─ (timer resets)
Call 3 ───┴─┬─ (timer resets)
└─────► Execute (after delay)Example
import { debounce } from 'bindra';
const search = debounce((query: string) => {
console.log('Searching for:', query);
// API call here
}, 300);
// User types quickly
search('a'); // Timer starts
search('al'); // Timer resets
search('ali'); // Timer resets
search('alic'); // Timer resets
search('alice'); // Timer resets
// After 300ms of no calls: "Searching for: alice"throttle()
Creates a throttled function that only executes at most once per specified time limit.
function throttle<T extends (...args: any[]) => any>(
fn: T,
limit: number
): (...args: Parameters<T>) => voidParameters
- fn (
Function) - The function to throttle - limit (
number) - Minimum time between executions in milliseconds
Returns
Throttled version of the function
How It Works
The function executes immediately on first call, then ignores subsequent calls until limit milliseconds have passed.
Call 1 ─► Execute immediately
Call 2 ─X (ignored - within limit)
Call 3 ─X (ignored - within limit)
└── (limit ms passes)
Call 4 ─► ExecuteExample
import { throttle } from 'bindra';
const handleScroll = throttle(() => {
console.log('Scroll position:', window.scrollY);
// Update UI based on scroll
}, 100);
window.addEventListener('scroll', handleScroll);
// Executes at most once per 100msdebounceWithMaxWait()
Creates a debounced function with a maximum wait time, ensuring execution at least once per max wait period.
function debounceWithMaxWait<T extends (...args: any[]) => any>(
fn: T,
delay: number,
maxWait: number
): (...args: Parameters<T>) => voidParameters
- fn (
Function) - The function to debounce - delay (
number) - Normal debounce delay in milliseconds - maxWait (
number) - Maximum time to wait before forced execution
Returns
Debounced function with max wait guarantee
How It Works
Combines debounce behavior with a maximum wait guarantee. If maxWait is reached, the function executes regardless of continued calls.
Call 1 ─┬─── (timers start)
Call 2 ─┴─┬─ (delay resets, maxWait continues)
Call 3 ───┴─┬─ (delay resets, maxWait continues)
└──────► Execute (maxWait reached)
OR
└────► Execute (delay reached)Example
import { debounceWithMaxWait } from 'bindra';
const autoSave = debounceWithMaxWait(
(data) => {
console.log('Auto-saving:', data);
// Save to API
},
1000, // Normal delay: 1 second
5000 // Max wait: 5 seconds
);
// User types continuously
autoSave({ content: 'a' });
autoSave({ content: 'ab' });
autoSave({ content: 'abc' });
// ... keeps typing for 5 seconds
// Forced save after 5 seconds, even if still typingUsage Examples
Search Input with Debounce
import { debounce } from 'bindra';
import { DataSource } from 'bindra';
interface Product {
id: number;
name: string;
price: number;
}
const products = new DataSource<Product>({
url: '/api/products'
});
// Debounce search to avoid excessive API calls
const debouncedSearch = debounce(async (query: string) => {
if (query.length < 2) return;
await products.fetch({
filter: { name: query }
});
}, 300); // Wait 300ms after user stops typing
// In your UI
searchInput.addEventListener('input', (e) => {
const query = (e.target as HTMLInputElement).value;
debouncedSearch(query);
});Scroll Handler with Throttle
import { throttle } from 'bindra';
const handleScroll = throttle(() => {
const scrollY = window.scrollY;
const windowHeight = window.innerHeight;
const docHeight = document.documentElement.scrollHeight;
// Check if near bottom
if (scrollY + windowHeight >= docHeight - 100) {
loadMoreContent();
}
// Update scroll indicator
updateScrollIndicator(scrollY);
}, 100); // Execute at most every 100ms
window.addEventListener('scroll', handleScroll);Auto-Save with Max Wait
import { debounceWithMaxWait } from 'bindra';
interface Document {
id: number;
content: string;
lastSaved: Date;
}
const documents = new DataSource<Document>({
url: '/api/documents'
});
// Auto-save with debounce but guarantee save every 5 seconds
const autoSave = debounceWithMaxWait(
async (id: number, content: string) => {
try {
await documents.update(id, { content });
showSaveIndicator('Saved');
} catch (error) {
showSaveIndicator('Failed to save');
}
},
1000, // Debounce: 1 second after user stops typing
5000 // Max wait: Save at least every 5 seconds
);
// In your editor
editor.addEventListener('input', (e) => {
const content = e.target.value;
autoSave(documentId, content);
showSaveIndicator('Saving...');
});Window Resize with Throttle
import { throttle } from 'bindra';
const handleResize = throttle(() => {
const width = window.innerWidth;
// Update layout based on width
if (width < 768) {
switchToMobileLayout();
} else if (width < 1024) {
switchToTabletLayout();
} else {
switchToDesktopLayout();
}
// Recalculate dimensions
recalculateLayout();
}, 200); // Execute at most every 200ms
window.addEventListener('resize', handleResize);Infinite Scroll with Throttle
import { throttle } from 'bindra';
const products = new DataSource<Product>({
url: '/api/products',
pagination: {
enabled: true,
pageSize: 20
}
});
const checkScroll = throttle(async () => {
const scrollBottom = window.scrollY + window.innerHeight;
const docHeight = document.documentElement.scrollHeight;
// Load more when 200px from bottom
if (scrollBottom >= docHeight - 200 && products.hasMore) {
showLoadingIndicator();
await products.fetchMore();
hideLoadingIndicator();
}
}, 300);
window.addEventListener('scroll', checkScroll);API Rate Limiting with Throttle
import { throttle } from 'bindra';
// Limit API calls to once per second
const updateUserStatus = throttle(async (status: string) => {
await fetch('/api/user/status', {
method: 'POST',
body: JSON.stringify({ status })
});
}, 1000); // Max 1 call per second
// User can spam button, but API only called once/second
statusButton.addEventListener('click', () => {
updateUserStatus('active');
});Form Validation with Debounce
import { debounce } from 'bindra';
const validateUsername = debounce(async (username: string) => {
if (username.length < 3) {
showError('Username too short');
return;
}
// Check availability on server
const response = await fetch(`/api/check-username?name=${username}`);
const { available } = await response.json();
if (!available) {
showError('Username taken');
} else {
showSuccess('Username available');
}
}, 500); // Wait 500ms after user stops typing
usernameInput.addEventListener('input', (e) => {
const username = (e.target as HTMLInputElement).value;
validateUsername(username);
});Mouse Move Tracking with Throttle
import { throttle } from 'bindra';
interface MousePosition {
x: number;
y: number;
timestamp: number;
}
const positions: MousePosition[] = [];
const trackMouse = throttle((e: MouseEvent) => {
positions.push({
x: e.clientX,
y: e.clientY,
timestamp: Date.now()
});
// Limit array size
if (positions.length > 100) {
positions.shift();
}
// Update heatmap or analytics
updateHeatmap(positions);
}, 50); // Track every 50ms
document.addEventListener('mousemove', trackMouse);Real-time Collaboration with DebounceWithMaxWait
import { debounceWithMaxWait } from 'bindra';
const syncDocument = debounceWithMaxWait(
async (content: string) => {
// Send to server
await fetch('/api/sync', {
method: 'POST',
body: JSON.stringify({ content })
});
// Broadcast to other users via WebSocket
ws.send(JSON.stringify({
type: 'document.update',
content
}));
},
500, // Normal: 500ms after user stops typing
3000 // Max: Sync at least every 3 seconds
);
editor.addEventListener('input', (e) => {
syncDocument(e.target.value);
});Best Practices
1. Choose the Right Tool
// ✅ Use debounce for user input
const search = debounce(searchFn, 300);
// ✅ Use throttle for continuous events
const scroll = throttle(scrollFn, 100);
// ✅ Use debounceWithMaxWait for auto-save
const save = debounceWithMaxWait(saveFn, 1000, 5000);2. Set Appropriate Delays
// ✅ Good delays
const search = debounce(fn, 300); // Search: 300ms
const scroll = throttle(fn, 100); // Scroll: 100ms
const resize = throttle(fn, 200); // Resize: 200ms
const autoSave = debounceWithMaxWait(fn, 1000, 5000); // Save: 1s/5s
// ❌ Too short - Still too many calls
const search = debounce(fn, 10);
// ❌ Too long - Poor UX
const search = debounce(fn, 5000);3. Store Function References
// ✅ Good - Can remove listener
const handleScroll = throttle(() => {
console.log('Scrolling');
}, 100);
window.addEventListener('scroll', handleScroll);
// Later...
window.removeEventListener('scroll', handleScroll);
// ❌ Bad - Can't remove
window.addEventListener('scroll', throttle(() => {
console.log('Scrolling');
}, 100));4. Consider Memory with Closures
// ✅ Good - Clean scope
const search = debounce(async (query: string) => {
const results = await fetchResults(query);
updateUI(results);
}, 300);
// ❌ Bad - Captures large objects
const largeData = new Array(1000000);
const search = debounce(() => {
// Closure captures largeData
console.log(largeData.length);
}, 300);5. Use MaxWait for Critical Operations
// ✅ Good - Guarantees periodic saves
const autoSave = debounceWithMaxWait(saveFn, 1000, 5000);
// ❌ Bad - Might never save if user keeps typing
const autoSave = debounce(saveFn, 1000);Performance Comparisons
Debounce vs Throttle
| Scenario | Best Choice | Reason |
|---|---|---|
| Search input | Debounce | Wait for user to finish typing |
| Scroll events | Throttle | Execute periodically while scrolling |
| Resize events | Throttle | Execute periodically while resizing |
| Auto-save | DebounceWithMaxWait | Wait for pause but guarantee periodic saves |
| Mouse move | Throttle | Track at regular intervals |
| Form validation | Debounce | Validate after user finishes field |
Timing Examples
Debounce (300ms):
Input: a -> ab -> abc -> abcd
Time: 0ms 50ms 100ms 150ms
Execute: ───► (450ms) "abcd"Throttle (300ms):
Scroll: ▼ ▼ ▼ ▼ ▼ ▼
Time: 0ms 50ms 100ms 150ms 200ms 250ms
Execute: ✓ ✓
0ms 300msDebounceWithMaxWait (300ms delay, 1000ms max):
Input: a -> ab -> abc -> ... -> continuous typing
Time: 0ms 50ms 100ms ... 1000ms
Execute: ───► (1000ms) - forced by maxWaitRelated Documentation
- DataSource - Use with DataSource operations
- Getting Started - Performance tips
- Examples - Real-world usage