見出し画像

GASのtry/catchでエラーが起きてもスクリプトが止まらない!実際に使えるエラーハンドリングの書き方まとめ

はじめに

前回の記事では、`getValues()` の結果をオブジェクト配列に変換することで、`row[2]` のようなインデックス参照を `row.sendFlag` のようなプロパティ名アクセスに書き換える方法をまとめました。

コードが何をしているかがそのまま読める——という点で、実際に書いてみると想像以上にすっきりするのを感じました。

今回は前回の「ひとこと」で予告していた、`try/catch` を使ったエラーハンドリングについてまとめます。

GASのスクリプトを本番で運用していると、こういう場面に出くわします。

「メール送信スクリプトを時間ベーストリガーで毎朝動かしていたのに、データの1件だけアドレスが不正な形式で、そこで処理が止まって残り全員に送れていなかった」

気づいたときには朝の時間が過ぎていた——みたいな状況です。「1件のエラーで全体が止まる」問題を回避するのが `try/catch` の役割です。

try/catch の基本構造

まず書き方から確認します。

try {
  // エラーが起きるかもしれない処理
} catch (e) {
  // エラーが起きたときだけ実行される処理
}

`try` の中の処理でエラーが発生すると、即座に `catch` へ処理が移ります。`catch` の引数 `e`(慣習的に `e` や `err` が使われます)にはエラーオブジェクトが渡されて、`e.message` でエラーの内容を文字列として取り出せます。

エラーが起きなければ `catch` の中は実行されません。

ループの内側に置くのが基本の使い方

一番よく使う場面は、複数行のデータを1件ずつ処理するループの中です。

function sendDailyReport() {
  const sheet = SpreadsheetApp.getActiveSpreadsheet().getSheetByName("データ");
  const values = sheet.getDataRange().getValues();

  // 前回まとめたオブジェクト配列への変換
  const rows = values.slice(1).map(row => ({
    name:     row[0],
    email:    row[1],
    sendFlag: row[2]
  }));

  const targets = rows.filter(row => row.sendFlag === "送信対象");

  targets.forEach(row => {
    try {
      GmailApp.sendEmail(
        row.email,
        `【ご確認】${row.name}さんへのご連絡`,
        `${row.name}さん、お世話になっております。`
      );
      console.log(`送信成功: ${row.name}(${row.email})`);
    } catch (e) {
      console.error(`送信失敗: ${row.name}(${row.email})/ ${e.message}`);
    }
  });
}

`forEach` の中の1件ずつを `try/catch` で囲んでいます。1件のメール送信でエラーが起きても、`catch` でログを残して次の行の処理に進みます——全体が止まりません。

ひとつ注意しておきたいのが、`try/catch` をループのに置いてしまうケースです。こうすると1件でエラーが起きた時点で残りの処理もまとめて止まります。「1件ずつ処理して、エラーは受け流しながら続けたい」ときは、ループの内側に書くのがポイントです。

エラーをSlack通知と組み合わせる

エラーが起きたとき、`console.error` でログを残すだけだと実行後に気づかない場合があります。以前の記事でまとめたSlack通知と組み合わせると、エラーをリアルタイムで受け取れます。

function notifySlack(message) {
  const webhookUrl = PropertiesService.getScriptProperties().getProperty("SLACK_WEBHOOK_URL");
  const payload = { text: message };
  UrlFetchApp.fetch(webhookUrl, {
    method: "post",
    contentType: "application/json",
    payload: JSON.stringify(payload)
  });
}

targets.forEach(row => {
  try {
    GmailApp.sendEmail(row.email, "件名", "本文");
  } catch (e) {
    const msg = `⚠️ 送信失敗: ${row.name}(${row.email})\nエラー: ${e.message}`;
    console.error(msg);
    notifySlack(msg);
  }
});

PropertiesService でWebhook URLを安全に管理する方法は、以前の記事でまとめました——こういうところでつながってきます。Slack通知があれば、スクリプトが止まらなくても「どの行でエラーが出たか」を即座に把握できます。

finally:成否に関わらず実行したい処理

`try/catch` には `finally` ブロックも追加できます。エラーが起きても起きなくても、必ず実行したい処理を書く場所です。

targets.forEach(row => {
  try {
    GmailApp.sendEmail(row.email, "件名", "本文");
  } catch (e) {
    console.error(`失敗: ${row.name} / ${e.message}`);
  } finally {
    // 成功・失敗どちらでも実行される
    console.log(`処理完了: ${row.name}`);
  }
});

GASの日常的な用途では `finally` まで必要になる場面はそこまで多くはないですが、「どんな結果でも必ず実行したい処理がある」ときに使えます。処理の終了ログを確実に残したいときなどが典型です。

まとめ

`try/catch` を使ったエラーハンドリングのポイントをまとめます。

  • `try` の中でエラーが起きると `catch` に処理が移る。`e.message` でエラーの詳細が取れる

  • ループの内側に `try/catch` を書くと、1件のエラーで全体が止まらずに続けられる

  • `console.error` だけでなく、Slack通知と組み合わせるとエラーをリアルタイムで把握できる

  • `finally` はエラーの有無に関わらず必ず実行したい処理に使う

  • `const`・アロー関数・オブジェクト配列変換と組み合わせると、読みやすく止まりにくいコードになる


【今日の私のひとこと】

try/catchは割と便利なので、よく使います。
また、最近はAIにコーディングを任せてもよく取り入れてくれますね。
ただ、そのtryを囲む範囲を大きくし過ぎると、どこで止まったかわからない場合もあるので、注意したいところです。

さて、次は、前回サラッと出してしまった「forEach・map・filter・reduceを使い分け」についてまとめていきたいと思います。


#GoogleAppsScript #GAS #業務効率化 #try /catch #スプレッドシート #note

いいなと思ったら応援しよう!