onEdit fires for every cell change — paste 40 rows and your automation thrashes. debounceOrSkip uses CacheService as a quiet window: if a token exists, return false (skip); else put the token and return true (run). Distinct from locks (locks wait/serialize concurrency; debounce drops rapid re-entry).
What you'll need
- Apps Script with CacheService (script or document cache)
- A noisy entrypoint (
onEdit, installable edit, rapid menu clicks) - A TTL that matches “human finished editing” (often 2–10 seconds)
How to use this snippet
/**
* Debounce an action via CacheService.
* @param {string} actionKey stable id for this action (e.g. 'onEdit:colB')
* @param {number=} ttlSeconds quiet window (default 5; CacheService max 21600)
* @return {boolean} true = proceed; false = skip (token already set)
*/
function debounceOrSkip(actionKey, ttlSeconds) {
ttlSeconds = ttlSeconds == null ? 5 : ttlSeconds;
var cache = CacheService.getScriptCache();
var token = 'debounce:' + actionKey;
if (cache.get(token)) return false;
cache.put(token, '1', Math.max(1, Math.min(ttlSeconds, 21600)));
return true;
}
Example
function onEdit(e) {
if (!e || !e.range) return;
// Only care about Status column changes
if (e.range.getColumn() !== 3) return;
if (!debounceOrSkip('onEdit:Status', 3)) {
console.log('onEdit: debounced');
return;
}
// … sync, label, notify …
}
Tips:
- Key by action, not by every cell — too-fine keys defeat the quiet window.
- Use document cache when each spreadsheet should debounce independently across bound scripts sharing a library (advanced); script cache is usually enough.
- Debounce ≠ lock: overlapping long work still needs
tryLockOrSkip/withScriptLock. - Installable
onEditcan do UrlFetch; simpleonEditcannot — debounce doesn’t change that. - TTL is best-effort; CacheService can evict early under pressure.
Tip: NitroGAS Co-Pilot can paste debounceOrSkip at the top of your edit handler and suggest an actionKey — you pick the TTL that matches how fast people edit.
Happy Coding!
