Scriptdrill

Getting started Updated November 2026

try, catch and finally

Wrap code that can fail in try { ... } catch (err) { ... } finally { ... }: catch receives an Error with err.message and err.stack, and finally runs either way. Catch around one risky operation per row (an API call, an email) so one failure does not stop the run, and log it.

Syntax

try { risky(); } catch (err) { Logger.log(err.message); } finally { cleanup(); }
PartWhat it is
err.messageThe error text, for example Invalid email
err.stackWhere it happened, with line numbers
throw new Error(text)Raise your own error; the run shows as failed in Executions

Examples

try {
  const ss = SpreadsheetApp.openById("not-a-real-id");
} catch (err) {
  Logger.log("Could not open: " + err.message);
}

Result: Could not open: Unexpected error while getting the method or property openById on object SpreadsheetApp.

rows.forEach((r, i) => {
  try {
    MailApp.sendEmail(r[1], "Hello " + r[0], "...");
    sheet.getRange(i + 2, 3).setValue("Sent");
  } catch (err) {
    sheet.getRange(i + 2, 3).setValue("Error: " + err.message);
  }
});

Result: Each row is marked Sent or Error: Invalid email, and the loop finishes

function safeRun() {
  try {
    mainJob();
  } catch (err) {
    MailApp.sendEmail("me@example.com", "Script failed", err.message + "\n" + err.stack);
    throw err;
  }
}

Result: You get an email with the stack trace and the execution still shows as failed

const lock = LockService.getScriptLock();
try {
  lock.waitLock(10000);
  // write to the sheet
} finally {
  lock.releaseLock();
}

Result: The lock is released even if the write throws

Common mistakes

  • A silent catch hides real problems: at least Logger.log(err.message) in every catch, or the script looks fine while doing nothing.
  • try catch does not stop Exceeded maximum execution time: that limit cannot be caught; split the work into chunks.
  • Catching the error but the row still shows Sent: the setValue("Sent") line sits before the risky call; mark success only after it returns.
  • catch (e) swallows the error and the trigger keeps running on bad data: rethrow with throw err when continuing makes no sense.

Practice try, catch and finally

These drills check your answer on live data, free, no account needed.

  • Retry a failing callCall `fn()` until it succeeds, at most `attempts` times, and resolve with its result. `fn` may return a Promise or a plain value, and it may throw or reject. If every attempt fails, reject with the error from the last attempt.
  • Timeout wrapperReturn a Promise that settles like `promise` if it settles within `ms` milliseconds. Otherwise reject with `new Error("Timed out")` at `ms`. Clear your timer once the result is known, so nothing is left pending. Tests run on a fake clock: `setTimeout`, `clearTimeout` and `Date.now` are simulated, so waits are instant and exact.
  • Run only onceReturn a function that calls `fn` the first time it is called, with that call's arguments, and returns `fn`'s result. Every later call returns that same first result without calling `fn` again, even when the result was `undefined` or `0`.

Questions

How do I retry a failed API call?

Loop a few times with try catch and Utilities.sleep between attempts: for (let i = 0; i < 3; i++) { try { return UrlFetchApp.fetch(url); } catch (err) { Utilities.sleep(1000 * (i + 1)); } }

What does muteHttpExceptions have to do with try catch?

Without it, UrlFetchApp throws on 4xx and 5xx responses and you need try catch; with it, you get the response and check getResponseCode instead.

Related

  • Logger.log and console.logDebug Google Apps Script with Logger.log and console.log: where output appears, format placeholders, logging arrays and objects with JSON.stringify, logs.
  • UrlFetchAppCall APIs from Google Apps Script with UrlFetchApp.fetch: GET and POST requests, headers and bearer tokens, JSON payloads, muteHttpExceptions, to a sheet.
  • Send email from a sheetSend email from Google Sheets with Apps Script: MailApp.sendEmail and GmailApp.sendEmail with HTML body, cc and attachments, loop a list of recipients, quota.
  • async, await and Promises in Apps ScriptAsync and Promises in Google Apps Script: the V8 runtime accepts async await syntax but every Google service is synchronous, so use UrlFetchApp.fetchAll.

Previous: Utilities.formatDateNext: Deploy as a web app