Files
arcum42-ComfyUI_SageUtils/js/shared/performanceUtils.js
T

298 lines
7.3 KiB
JavaScript

/**
* Performance Utilities
*
* Provides debouncing, throttling, and rate limiting utilities
* to optimize performance and prevent overwhelming the system
*/
/**
* Debounce function - delays execution until after wait time has elapsed since last call
* @param {Function} func - Function to debounce
* @param {number} wait - Wait time in milliseconds
* @param {boolean} immediate - Execute on leading edge instead of trailing
* @returns {Function} Debounced function
*/
export function debounce(func, wait, immediate = false) {
let timeout;
return function executedFunction(...args) {
const context = this;
const later = () => {
timeout = null;
if (!immediate) func.apply(context, args);
};
const callNow = immediate && !timeout;
clearTimeout(timeout);
timeout = setTimeout(later, wait);
if (callNow) func.apply(context, args);
};
}
/**
* Throttle function - limits execution to once per wait period
* @param {Function} func - Function to throttle
* @param {number} wait - Wait time in milliseconds
* @returns {Function} Throttled function
*/
export function throttle(func, wait) {
let inThrottle;
let lastFunc;
let lastRan;
return function(...args) {
const context = this;
if (!inThrottle) {
func.apply(context, args);
lastRan = Date.now();
inThrottle = true;
} else {
clearTimeout(lastFunc);
lastFunc = setTimeout(() => {
if ((Date.now() - lastRan) >= wait) {
func.apply(context, args);
lastRan = Date.now();
}
}, Math.max(wait - (Date.now() - lastRan), 0));
}
};
}
/**
* Rate Limiter class - limits calls to a function over time
*/
export class RateLimiter {
constructor(maxCalls, timeWindow) {
this.maxCalls = maxCalls;
this.timeWindow = timeWindow; // in milliseconds
this.calls = [];
}
/**
* Check if a call is allowed
* @returns {boolean} True if call is allowed
*/
allowCall() {
const now = Date.now();
// Remove calls outside the time window
this.calls = this.calls.filter(time => now - time < this.timeWindow);
// Check if we're under the limit
if (this.calls.length < this.maxCalls) {
this.calls.push(now);
return true;
}
return false;
}
/**
* Get time until next call is allowed
* @returns {number} Milliseconds until next call allowed, or 0 if allowed now
*/
getWaitTime() {
if (this.calls.length < this.maxCalls) {
return 0;
}
const now = Date.now();
const oldestCall = this.calls[0];
return Math.max(0, this.timeWindow - (now - oldestCall));
}
/**
* Reset the rate limiter
*/
reset() {
this.calls = [];
}
}
/**
* Batch processor - collects items and processes them in batches
*/
export class BatchProcessor {
constructor(processFn, options = {}) {
this.processFn = processFn;
this.batchSize = options.batchSize || 10;
this.delay = options.delay || 100;
this.items = [];
this.timeout = null;
}
/**
* Add item to batch
* @param {*} item - Item to add
*/
add(item) {
this.items.push(item);
// Process immediately if batch is full
if (this.items.length >= this.batchSize) {
this.flush();
} else {
// Otherwise schedule processing
this.scheduleProcess();
}
}
/**
* Schedule batch processing
*/
scheduleProcess() {
if (this.timeout) {
clearTimeout(this.timeout);
}
this.timeout = setTimeout(() => {
this.flush();
}, this.delay);
}
/**
* Process all pending items immediately
*/
flush() {
if (this.timeout) {
clearTimeout(this.timeout);
this.timeout = null;
}
if (this.items.length > 0) {
const itemsToProcess = [...this.items];
this.items = [];
try {
const result = this.processFn(itemsToProcess);
// Capture async failures for promise-returning batch handlers.
if (result && typeof result.then === 'function') {
result.catch(error => {
console.error('[BatchProcessor] Error processing async batch:', error);
});
}
} catch (error) {
console.error('[BatchProcessor] Error processing batch:', error);
}
}
}
/**
* Get current batch size
* @returns {number} Number of pending items
*/
getPendingCount() {
return this.items.length;
}
/**
* Clear pending items without processing
*/
clear() {
if (this.timeout) {
clearTimeout(this.timeout);
this.timeout = null;
}
this.items = [];
}
}
/**
* Request Animation Frame throttle - limits execution to once per animation frame
* @param {Function} func - Function to throttle
* @returns {Function} Throttled function
*/
export function rafThrottle(func) {
let rafId = null;
let lastArgs = null;
return function(...args) {
lastArgs = args;
if (rafId === null) {
rafId = requestAnimationFrame(() => {
func.apply(this, lastArgs);
rafId = null;
lastArgs = null;
});
}
};
}
/**
* Memoize function results with LRU cache
* @param {Function} func - Function to memoize
* @param {number} maxSize - Maximum cache size
* @returns {Function} Memoized function
*/
export function memoize(func, maxSize = 100) {
const cache = new Map();
const keyOrder = [];
return function(...args) {
const key = JSON.stringify(args);
if (cache.has(key)) {
return cache.get(key);
}
const result = func.apply(this, args);
cache.set(key, result);
keyOrder.push(key);
// LRU eviction
if (keyOrder.length > maxSize) {
const oldestKey = keyOrder.shift();
cache.delete(oldestKey);
}
return result;
};
}
/**
* Create a lazy loader that only executes once
* @param {Function} loaderFn - Function to execute once
* @returns {Function} Lazy loader function
*/
export function lazy(loaderFn) {
let loaded = false;
let result = null;
let promise = null;
return async function() {
if (loaded) {
return result;
}
if (promise) {
return promise;
}
promise = (async () => {
result = await loaderFn();
loaded = true;
promise = null;
return result;
})();
return promise;
};
}
export default {
debounce,
throttle,
RateLimiter,
BatchProcessor,
rafThrottle,
memoize,
lazy
};