Cancel Flag

A teammate kicked off “Process all rows” from the menu. Two minutes in they realize the filter was wrong. There is no Stop button — only waiting for the 6-minute kill, or closing the tab and hoping. requestCancel(key) / isCancelRequested(key) / clearCancel(key) put a short-lived cancel flag in CacheService. A sidebar (or another menu item) sets it; your job loop checks it each iteration and breaks cleanly; finally clears it. If nobody clears it, the TTL expires on its own.

When to use

  • Long menu / sidebar jobs a human might want to abort mid-run
  • Time-driven jobs you occasionally need to pause from a “Cancel tonight’s run” control
  • Any loop that already checks underTimeBudget — add cancel as a second exit condition

How to use this snippet

var CANCEL_PREFIX_ = 'cancel:'; /** Bound: per-spreadsheet cache. Standalone: script-wide cache. */ function cancelCache_() { return CacheService.getDocumentCache() || CacheService.getScriptCache(); } /** * Raise the cancel flag for this job key. * @param {string} key short id shared by the UI and the job, e.g. 'nightlySync' * @param {number=} ttlSeconds default 21600 (6h). Safety net if clearCancel never runs. */ function requestCancel(key, ttlSeconds) { var ttl = Math.max(1, Math.min(ttlSeconds || 21600, 21600)); cancelCache_().put(CANCEL_PREFIX_ + String(key), '1', ttl); } /** True if a cancel has been requested for this key (and not yet cleared / expired). */ function isCancelRequested(key) { return cancelCache_().get(CANCEL_PREFIX_ + String(key)) === '1'; } /** Lower the flag. Call from the job's finally (and from a "Resume" control if you have one). */ function clearCancel(key) { cancelCache_().remove(CANCEL_PREFIX_ + String(key)); }

Example

var JOB_KEY = 'processVisible'; // Menu / sidebar "Cancel" button calls this (google.script.run.requestCancel) function cancelProcessVisible() { requestCancel(JOB_KEY); SpreadsheetApp.getActive().toast('Cancel requested — the job will stop after the current row.', 'Cancel', 5); } function processVisibleRows() { clearCancel(JOB_KEY); // fresh run var started = new Date(); try { var rows = getVisibleRowObjects(SpreadsheetApp.getActiveSheet()); for (var i = 0; i < rows.length; i++) { if (!underTimeBudget(started) || isCancelRequested(JOB_KEY)) break; doWork_(rows[i]); } } finally { clearCancel(JOB_KEY); } }

Tips:

  • Use one stable key per job ('nightlySync', 'processVisible'). Don’t invent a new key per run or Cancel can’t find the job.
  • Check the flag between units of work (per row / per file), not inside a single 30-second UrlFetch — you can’t interrupt that mid-call.
  • Always clearCancel in finally. The TTL is a safety net for crashed UIs, not the happy path.
  • CacheService is best-effort and not instantaneous across all servers; a check every row is fine, don’t spin on it.
  • Pair with under-time-budget so Cancel and the 6-minute wall share one loop exit.

Tip: NitroGAS Co-Pilot can wire a tiny HtmlService sidebar Cancel button to requestCancel and add the isCancelRequested check to an existing loop — paste the loop and say which key to use.

Happy Coding!

NitroGAS Chrome Extension

Want this snippet handy inside the Apps Script editor? NitroGAS gives you free themes and snippets — plus optional Co-Pilot when you want a boost (1-week, 1-month, or yearly passes — no subscription). Happy Coding!

Get the Extension