Naar inhoud
lightbulb Welkom op de nieuwe kennisbank | We hebben de docs volledig vernieuwd met meer dan 160 features. Bekijk wat nieuw isarrow_forward

Foutafhandeling in Apps Script

Schrijf robuuste Apps Script code met try/catch, aangepaste fouttypes en herstelstrategieen voor productie-scripts.

Basis try/catch/finally

Apps Script draait op de V8-runtime en gebruikt de standaard JavaScript-foutafhandeling. Elke onafgevangen fout stopt de scriptuitvoering en verschijnt in het uitvoeringslogboek (Execution log) in de editor.

function basisFoutafhandeling() {
  try {
    const data = haalDataOpVanApi();
    verwerkData(data);
  } catch(e) {
    Logger.log(`Fout: ${e.message}`);
    Logger.log(`Stack: ${e.stack}`);
    throw e;
  } finally {
    Logger.log('Altijd uitgevoerd, ook bij fout');
  }
}

function haalDataOpVanApi() {
  const response = UrlFetchApp.fetch('https://api.voorbeeld.nl/data', {
    muteHttpExceptions: true,
  });

  if (response.getResponseCode() !== 200) {
    throw new Error(`API-fout: HTTP ${response.getResponseCode()}`);
  }

  return JSON.parse(response.getContentText());
}
warning

Zet muteHttpExceptions altijd aan

Zonder muteHttpExceptions: true gooit UrlFetchApp.fetch() zelf een fout bij een HTTP-status van 400 of hoger, voordat jij de statuscode kunt lezen. Met de optie aan krijg je het volledige response-object terug en bepaal je zelf hoe je per statuscode reageert.

Aangepaste fouttypen

Door eigen fouttypen te definieren kun je in een catch-blok gericht reageren op het soort fout. Zet bijvoorbeeld een vlag herstelbaar op de fout, zodat een retry-laag later weet of opnieuw proberen zinvol is.

class ValidationFout extends Error {
  constructor(veld, bericht) {
    super(`Validatiefout in ${veld}: ${bericht}`);
    this.name = 'ValidationFout';
    this.veld = veld;
  }
}

class ApiVerbindingsFout extends Error {
  constructor(statusCode, url) {
    super(`API fout ${statusCode} bij ${url}`);
    this.name = 'ApiVerbindingsFout';
    this.statusCode = statusCode;
    this.url = url;
    this.herstelbaar = statusCode >= 500;
  }
}

function verwerkInputData(data) {
  if (!data.naam || data.naam.trim() === '') {
    throw new ValidationFout('naam', 'Naam is verplicht');
  }
  if (!data.email || !data.email.includes('@')) {
    throw new ValidationFout('email', 'Ongeldig e-mailadres');
  }
}
info

Controleer altijd e.name

De V8-runtime ondersteunt class-gebaseerde fouttypen volledig, inclusief overerving van Error. Controleer in een catch-blok e.name om het type fout te bepalen. Vermijd instanceof over scriptgrenzen heen, want bij bibliotheken kan dat onbetrouwbaar zijn; een vergelijking op e.name werkt overal.

Fouttype-specifieke afhandeling

function verwerkMetFoutafhandeling(invoer) {
  try {
    verwerkInputData(invoer);
    const resultaat = verwerkVoorApi(invoer);
    return {succes: true, data: resultaat};
  } catch(e) {
    if (e.name === 'ValidationFout') {
      Logger.log(`Validatiefout, geen retry nodig: ${e.message}`);
      return {succes: false, fout: e.message, herstelbaar: false};
    }

    if (e.name === 'ApiVerbindingsFout') {
      if (e.herstelbaar) {
        Logger.log('Serverfout, retry mogelijk');
        return {succes: false, fout: e.message, herstelbaar: true};
      }
      Logger.log('Clientfout, geen retry');
      return {succes: false, fout: e.message, herstelbaar: false};
    }

    Logger.log(`Onverwachte fout: ${e.message}`);
    throw e;
  }
}

Retry-logica voor tijdelijke fouten

Tijdelijke (transiente) fouten zoals een 500-status of een tijdelijke timeout lossen vaak vanzelf op. Een retry met oplopende wachttijd (exponentiele of lineaire backoff) maakt je script veerkrachtig zonder dat je een externe API overbelast.

function metRetry(fn, maxPogingen = 3, wachtMs = 1000) {
  let laatsteFout;

  for (let poging = 1; poging <= maxPogingen; poging++) {
    try {
      return fn();
    } catch(e) {
      laatsteFout = e;

      const herstelbaar = e.name === 'ApiVerbindingsFout' && e.herstelbaar;
      if (!herstelbaar || poging === maxPogingen) {
        break;
      }

      Logger.log(`Poging ${poging} mislukt: ${e.message}. Wachten ${wachtMs * poging}ms`);
      Utilities.sleep(wachtMs * poging);
    }
  }

  throw laatsteFout;
}

function roepApiAanMetRetry() {
  return metRetry(() => {
    const response = UrlFetchApp.fetch('https://api.voorbeeld.nl/data', {muteHttpExceptions: true});
    if (response.getResponseCode() >= 500) {
      throw new ApiVerbindingsFout(response.getResponseCode(), 'https://api.voorbeeld.nl/data');
    }
    return JSON.parse(response.getContentText());
  });
}
lightbulb

Let op de uitvoeringslimiet

Een Apps Script-uitvoering mag maximaal zes minuten draaien (dertig minuten voor sommige Workspace-abonnementen). Bouw je retry-laag zo dat de som van alle wachttijden ruim binnen die limiet blijft, anders breekt het script af met de melding "Exceeded maximum execution time" terwijl je nog aan het wachten bent.

Batch-verwerking met gedeeltelijke foutafhandeling

Bij het verwerken van een lijst wil je vaak dat een fout in een enkel item de hele batch niet stopt. Vang per item op, verzamel de resultaten en rapporteer aan het eind wat geslaagd en wat mislukt is.

function verwerkBatchMetFouten(items) {
  const resultaten = {
    geslaagd: [],
    mislukt: [],
    totaal: items.length,
  };

  items.forEach((item, idx) => {
    try {
      const resultaat = verwerkEnkelItem(item);
      resultaten.geslaagd.push({idx, item, resultaat});
    } catch(e) {
      resultaten.mislukt.push({idx, item, fout: e.message});
      Logger.log(`Item ${idx} mislukt: ${e.message}`);
    }
  });

  Logger.log(`Geslaagd: ${resultaten.geslaagd.length}, Mislukt: ${resultaten.mislukt.length}`);
  return resultaten;
}

function verwerkEnkelItem(item) {
  if (!item.naam) throw new Error('Naam ontbreekt');
  return {verwerkt: true, naam: item.naam};
}

Globale foutafhandelaar voor triggers

Bij een tijdgestuurde trigger is er niemand die het foutscherm ziet. Wikkel de logica daarom in een wrapper die fouten opvangt, naar Cloud Logging schrijft met console.error() en de beheerder mailt. De wrapper gooit zelf geen fout, zodat de trigger-run netjes eindigt.

function triggerWrapper(fn, functieNaam) {
  const startTijd = Date.now();
  try {
    fn();
    Logger.log(`${functieNaam} succesvol in ${Date.now() - startTijd}ms`);
  } catch(e) {
    const melding = `Trigger fout in ${functieNaam}: ${e.message}`;
    console.error(melding);

    try {
      GmailApp.sendEmail(
        PropertiesService.getScriptProperties().getProperty('ADMIN_EMAIL') || 'admin@bedrijf.nl',
        `Script Fout: ${functieNaam}`,
        `${melding}

Stack:
${e.stack}`
      );
    } catch(mailFout) {
      console.error('Kon foutmelding niet verzenden: ' + mailFout.message);
    }
  }
}

function dagelijkseVerwerking() {
  triggerWrapper(() => {
    Logger.log('Dagelijkse verwerking gestart');
  }, 'dagelijkseVerwerking');
}

Berichten die je met console.error() schrijft, verschijnen in Cloud Logging (voorheen Stackdriver) en in het Executions-paneel van de editor. Vink in de Google Cloud-instellingen van je project "Log uncaught exceptions" aan om onafgevangen fouten ook automatisch in Error Reporting te zien.

Robuuste trigger met foutafhandeling

  1. Maak een triggerWrapper(fn, naam) die alle fouten opvangt.
  2. Log fouten naar Cloud Logging met console.error().
  3. Stuur een e-mail naar de beheerder bij kritieke fouten.
  4. Schrijf een audit-regel naar een log-sheet voor latere analyse.
  5. Gooi de fout NIET opnieuw, zodat de trigger-run succesvol eindigt en niet herhaaldelijk opnieuw wordt geprobeerd.
Hoe stop ik een script netjes bij een kritieke fout?

Gebruik throw new Error('beschrijving') om de uitvoering te stoppen en een foutmelding te loggen. Apps Script logt onafgevangen fouten automatisch in het uitvoeringslogboek.

Wat is het verschil tussen throw en return bij fouten?

throw stopt de uitvoering en geeft de fout door naar boven. return geeft een waarde terug, bijvoorbeeld {succes: false}. Gebruik throw voor onverwachte of kritieke fouten en return voor verwachte negatieve uitkomsten die de aanroeper rustig kan afhandelen.

Hoe voorkom ik dat een fout in een trigger mijn script volledig stopt?

Gebruik een triggerWrapper() die alle fouten opvangt, logt en notificeert maar zelf geen fout gooit. De trigger-run eindigt dan netjes en wordt niet steeds opnieuw uitgevoerd.

Kan ik Error-subklassen gebruiken in Apps Script?

Ja. De V8-runtime ondersteunt volledige ES6-class-hierarchieen, inclusief overerving van Error. Zet eigen velden op de subklasse, zoals een statuscode of een vlag herstelbaar.

Waarom zie ik mijn fout niet in het logboek?

Controleer of je niet per ongeluk de fout opvangt en negeert in een leeg catch-blok. Gebruik daarnaast console.error() in plaats van alleen Logger.log() voor fouten, want console-berichten blijven via Cloud Logging langer bewaard dan de korte logregels van Logger.

Hoe handel ik een API-aanroep af die soms een 429 (te veel verzoeken) geeft?

Behandel een 429 als tijdelijke fout en doe een retry met oplopende wachttijd. Respecteer waar mogelijk de Retry-After-header uit het response en blijf binnen de uitvoeringslimiet van het script.

Goede foutafhandeling is het verschil tussen een productie-script en een fragiel experiment. Investeer in consistente patronen voor alle scripts die automatisch worden uitgevoerd, dan weet je bij elke trigger-run precies wat er goed en wat er fout ging.