Files
rspade_system/app/RSpade/Core/Js/async.js
root f6fac6c4bc Fix bin/publish: copy docs.dist from project root
Fix bin/publish: use correct .env path for rspade_system
Fix bin/publish script: prevent grep exit code 1 from terminating script

🤖 Generated with [Claude Code](https://claude.com/claude-code)

Co-Authored-By: Claude <noreply@anthropic.com>
2025-10-21 02:08:33 +00:00

162 lines
5.2 KiB
JavaScript
Executable File

/*
* Async utility functions for the RSpade framework.
* These functions handle asynchronous operations, delays, debouncing, and mutexes.
*/
// ============================================================================
// ASYNC UTILITIES
// ============================================================================
/**
* Pauses execution for specified milliseconds
* @param {number} [milliseconds=0] - Delay in milliseconds (0 uses requestAnimationFrame)
* @returns {Promise<void>} Promise that resolves after delay
* @example await sleep(1000); // Wait 1 second
*/
function sleep(milliseconds = 0) {
return new Promise((resolve) => {
if (milliseconds == 0 && requestAnimationFrame) {
requestAnimationFrame(resolve);
} else {
setTimeout(resolve, milliseconds);
}
});
}
/**
* Creates a debounced function with exclusivity and promise fan-in
*
* This function, when invoked, immediately runs the callback exclusively.
* For subsequent invocations, it applies a delay before running the callback exclusively again.
* The delay starts after the current asynchronous operation resolves.
*
* If 'delay' is set to 0, the function will only prevent enqueueing multiple executions of the
* same method more than once, but will still run them immediately in an exclusive sequential manner.
*
* The most recent invocation of the function will be the parameters that get passed to the function
* when it invokes.
*
* The function returns a promise that resolves when the next exclusive execution completes.
*
* @param {function} callback The callback function to be invoked
* @param {number} delay The delay in milliseconds before subsequent invocations
* @param {boolean} immediate if true, the first time the action is called, the callback executes immediately
* @returns {function} A function that when invoked, runs the callback immediately and exclusively,
*
* @decorator
*/
function debounce(callback, delay, immediate = false) {
let running = false;
let queued = false;
let last_end_time = 0; // timestamp of last completed run
let timer = null;
let next_args = [];
let resolve_queue = [];
let reject_queue = [];
const run_function = async () => {
const these_resolves = resolve_queue;
const these_rejects = reject_queue;
const args = next_args;
resolve_queue = [];
reject_queue = [];
next_args = [];
queued = false;
running = true;
try {
const result = await callback(...args);
for (const resolve of these_resolves) resolve(result);
} catch (err) {
for (const reject of these_rejects) reject(err);
} finally {
running = false;
last_end_time = Date.now();
if (queued) {
clearTimeout(timer);
timer = setTimeout(run_function, Math.max(delay, 0));
} else {
timer = null;
}
}
};
return function (...args) {
next_args = args;
return new Promise((resolve, reject) => {
resolve_queue.push(resolve);
reject_queue.push(reject);
// Nothing running and nothing scheduled
if (!running && !timer) {
const first_call = last_end_time === 0;
if (immediate && first_call) {
run_function();
return;
}
const since = first_call ? Infinity : Date.now() - last_end_time;
if (since >= delay) {
run_function();
} else {
const wait = Math.max(delay - since, 0);
clearTimeout(timer);
timer = setTimeout(run_function, wait);
}
return;
}
// If we're already running or a timer exists, just mark queued.
// The finally{} of run_function handles scheduling after full delay.
queued = true;
});
};
}
// ============================================================================
// READ-WRITE LOCK FUNCTIONS - Delegated to ReadWriteLock class
// ============================================================================
/**
* Acquire an exclusive write lock by name.
* Only one writer runs at a time; blocks readers until finished.
* @param {string} name
* @param {() => any|Promise<any>} cb
* @returns {Promise<any>}
*/
function rwlock(name, cb) {
return ReadWriteLock.acquire(name, cb);
}
/**
* Acquire a shared read lock by name.
* Multiple readers run in parallel, but readers are blocked by queued/active writers.
* @param {string} name
* @param {() => any|Promise<any>} cb
* @returns {Promise<any>}
*/
function rwlock_read(name, cb) {
return ReadWriteLock.acquire_read(name, cb);
}
/**
* Forcefully clear all locks and queues for a given name.
* @param {string} name
*/
function rwlock_force_unlock(name) {
ReadWriteLock.force_unlock(name);
}
/**
* Inspect lock state for debugging.
* @param {string} name
* @returns {{readers:number, writer_active:boolean, reader_q:number, writer_q:number}}
*/
function rwlock_pending(name) {
return ReadWriteLock.pending(name);
}