Apps Script kills a execution around the 6-minute wall. If your loop is still mid-UrlFetch when that happens, you lose the chance to write a checkpoint, clear a lock, or stamp “partial”. underTimeBudget(startedAt, budgetMs?) returns true while you’re still under the budget (default 5.5 minutes / 330000 ms), so the loop can stop cleanly, save progress, and let the next trigger finish the job.
When to use
- Row / file / page loops inside a time-driven or menu job that might overrun
- Checkpointed imports and “process the queue until we should stop” patterns
- Any job that needs 15–30 seconds at the end to flush, unlock, and stamp status
How to use this snippet
/**
* True while wall time since startedAt is still under the budget.
* Default budget: 330000 ms (5.5 minutes) — leaves headroom under the ~6 min limit.
*
* @param {Date|number} startedAt Date or epoch ms when the run began
* @param {number=} budgetMs override budget in milliseconds
* @return {boolean}
*/
function underTimeBudget(startedAt, budgetMs) {
var budget = budgetMs == null ? 330000 : Number(budgetMs);
var start = startedAt instanceof Date ? startedAt.getTime() : Number(startedAt);
if (!isFinite(start) || !isFinite(budget) || budget <= 0) return false;
return (Date.now() - start) < budget;
}
Example
function processQueueUntilBudget() {
var started = new Date();
var lock = LockService.getScriptLock();
if (!lock.tryLock(5000)) return; // someone else is running
try {
var sheet = SpreadsheetApp.getActive().getSheetByName('Queue');
var rows = claimPendingRows_(sheet); // your claim helper
var i = 0;
while (i < rows.length && underTimeBudget(started)) {
// Optional: also honor a Cancel flag from a sidebar
// if (isCancelRequested('queueJob')) break;
processOne_(rows[i]);
markDone_(sheet, rows[i]);
i++;
}
if (i < rows.length) {
// Stopped early on purpose — next trigger continues
stampStatus_('Partial: ' + i + ' of ' + rows.length + ' this run');
} else {
stampStatus_('OK: ' + i + ' rows');
}
} finally {
lock.releaseLock();
}
}
Tips:
- Capture
startedAtonce at the top of the run, not inside the loop. - Shorter budget (e.g. 4 minutes) if each iteration can be slow or you need a long teardown.
- This is wall-clock time, not CPU. A single hung
UrlFetchcan still blow the limit — keep per-call timeouts tight. - Pair with a checkpoint (Script Property or a cursor cell) so the next trigger resumes where you left off.
- Pair with a cancel flag when a human might hit Cancel mid-run.
Tip: NitroGAS Co-Pilot can wrap an existing for loop with var started = new Date() and an underTimeBudget(started) check, plus a one-line checkpoint write when the loop breaks early.
Happy Coding!
