feat: Prevent UI hanging during video save or playlist add
Task #1 completed by ClaudeQueue ClaudeQueue
This commit is contained in:
45
frontend/async-guard.js
Normal file
45
frontend/async-guard.js
Normal file
@@ -0,0 +1,45 @@
|
||||
/* ============================================================================
|
||||
* async-guard — tiny concurrency helper shared by the frontend.
|
||||
*
|
||||
* Keeps long-running native operations (offline-cache download/delete, playlist
|
||||
* mutations) from being triggered twice at once for the same target, so the UI
|
||||
* stays responsive and on-device state can't be corrupted by rapid clicks.
|
||||
*
|
||||
* Framework-free and dependency-free on purpose:
|
||||
* • Loads as a plain <script> under CSP `script-src 'self'` (browser global
|
||||
* `window.AsyncGuard`).
|
||||
* • `require`-able by `node --test` (CommonJS `module.exports`).
|
||||
* ========================================================================== */
|
||||
(function (root) {
|
||||
'use strict';
|
||||
|
||||
/**
|
||||
* Run `fn` exclusively for `key`: if an operation for `key` is already
|
||||
* in-flight (tracked in `set`), this call is a no-op and resolves to
|
||||
* `undefined`. Otherwise `key` is added to `set`, `fn` runs, and `key` is
|
||||
* removed once it settles — on success AND on failure — so a failed
|
||||
* operation can be retried.
|
||||
*
|
||||
* @param {Set<string>} set in-flight key registry (caller owns it)
|
||||
* @param {string} key identifies the target being operated on
|
||||
* @param {() => (any|Promise<any>)} fn the work to run exclusively
|
||||
* @returns {Promise<any>} fn's resolved value, or undefined if skipped
|
||||
*/
|
||||
async function runExclusive(set, key, fn) {
|
||||
if (set.has(key)) return undefined;
|
||||
set.add(key);
|
||||
try {
|
||||
return await fn();
|
||||
} finally {
|
||||
set.delete(key);
|
||||
}
|
||||
}
|
||||
|
||||
const AsyncGuard = { runExclusive };
|
||||
|
||||
if (typeof module !== 'undefined' && module.exports) {
|
||||
module.exports = AsyncGuard;
|
||||
} else {
|
||||
root.AsyncGuard = AsyncGuard;
|
||||
}
|
||||
})(typeof globalThis !== 'undefined' ? globalThis : this);
|
||||
Reference in New Issue
Block a user