メディア掲載: レバテックフリーランス様のサイトで当ブログが紹介されました

GASでエラーが発生しても処理を続ける(例外処理)

Google Apps Scriptに限ったことではありませんが、コーディングミスや、その他様々な理由によってエラーが発生することがあります。

例えば、Forなどによる繰り返し処理の途中でエラーが発生してしまうと、処理が中断されてしまい困ることがあります。
また、利用者に理解できないメッセージが表示されてしまうなど、不安にさせてしまうこともあります。

この記事では、その対処方法として、エラー発生時の例外処理の書き方を説明します。

  • try...catch文を使えば、エラー発生時の処理を記述できる
  • throw new Error("メッセージ")で独自の例外を発生させられる
  • HtmlServiceでGAS側のエラーを扱うにはwithFailureHandlerを使う

解説用サンプルツール

この記事でサンプルとして利用するツールについて説明します。

今回のツールは、指定したシートのA1セルの値を取得するツールです。

  1. メインシートのA列に、対象のシート名を入力する
  2. スクリプトエディタから myFunction関数 を実行する
  3. メインシートのB列に、対象シートのA1セルの値が転記される

画面イメージ(スプレッドシート)は以下のとおりです。

GASでエラー処理を試すためのサンプルツール。
メインシートはこんな感じ。A列に対象とするシート名を入力する。
GASでエラー処理を試すためのサンプルツール。(各シート)
シート1、シート2、シート3は、A1セルに値が入力されている。

実行するスクリプトは下記のとおりです。(この時点では例外処理はしていません)

const ss = SpreadsheetApp.getActiveSpreadsheet();

function myFunction() {
  const mainSheet = ss.getSheetByName("メイン");
  const lastRow = mainSheet.getLastRow();
  for (let i = 2; i <= lastRow; i++) {
    const sheetName = mainSheet.getRange(i, 1).getValue();
    // 対象シートのA1セルの値を取得
    const a1Value = getA1Value(sheetName);
    // B列に値を転記
    mainSheet.getRange(i, 2).setValue(a1Value);
  }
}

function getA1Value(sheetName) {
  const sheet = ss.getSheetByName(sheetName);
  return sheet.getRange("A1").getValue();
}

myFunction関数を実行すると、下図のように、B列に結果が入力されます。

GASでエラー処理を試すためのサンプルツール。スクリプト実行後。

このスクリプトでは、存在しないシート名を指定するとエラーが発生するはずです。
A列(シート名)に無いシートと入力してスクリプトを実行してみます。

GASでエラー処理を試すためのサンプルツール。スクリプト(エラー)実行後。
「無いシート」の結果は取得できず、その後の「シート3」も値が取得されていない。
スクリプトエディタで確認できる実行ログ。TypeError: Cannot read property 'getRange' of null というエラーが発生している。
実行ログにエラーが出力された

無いシートというシートが存在しないため、getRangeメソッドがエラーとなって処理が中断されました。

try…catch文で例外対応

エラーが発生しても処理を継続するためには、try…catch文を使用します。
修正したコードは下記のとおりです。

const ss = SpreadsheetApp.getActiveSpreadsheet();

function myFunction() {
  const mainSheet = ss.getSheetByName("メイン");
  const lastRow = mainSheet.getLastRow();
  for (let i = 2; i <= lastRow; i++) {
    const sheetName = mainSheet.getRange(i, 1).getValue();
    try {
      // 対象シートのA1セルの値を取得
      const a1Value = getA1Value(sheetName);
      // B列に値を転記
      mainSheet.getRange(i, 2).setValue(a1Value);
    } catch(error) {
      // B列にエラー内容を書き込む
      mainSheet.getRange(i, 2).setValue(error.name + ": " + error.message);
    }
  }
}

function getA1Value(sheetName) {
  const sheet = ss.getSheetByName(sheetName);
  return sheet.getRange("A1").getValue();
}

try { … }の処理を実行していき、途中でエラーが発生した場合は、try { … }内の処理はそこで中断し、catch(error) { … }の処理を実行します。

無いシートを指定したときの流れは、下記のような感じです。

  1. 行10: getA1Value関数を呼ぶ
  2. 行22: getRangeでエラーになる
  3. tryブロックの行11以降は実行されず、catchブロックの処理が行われる
  4. 行15: 受け取った例外オブジェクト(error)の内容をセルに書き込む

このコードを実行した後のスプレッドシートはこんな感じです。

try...catch文で例外処理をした場合。シートにエラーの内容が転記され、その後の処理は正常に終了している。
「無いシート」に対してエラー内容が記載される。なお、それ以降の行も実行されている。

エラー発生時の例外処理(エラー内容の書き込み)ができていて、それ以降の行も処理されたことが確認できました。

try…catch文の詳細が知りたい場合は、こちらのドキュメントが参考になります。

必ず実行する処理はfinallyブロックへ

tryブロックとcatchブロックの他に、finallyブロックがあります。
finallyブロックは、例外が発生してもしなくても実行されます。

サンプルスクリプトにおいては、B列に結果を書き込むという処理は、例外が発生してもしなくても行う処理でしたので、コードを修正してfinallyブロックに入れてみました。

const ss = SpreadsheetApp.getActiveSpreadsheet();

function myFunction() {
  const mainSheet = ss.getSheetByName("メイン");
  const lastRow = mainSheet.getLastRow();
  for (let i = 2; i <= lastRow; i++) {
    const sheetName = mainSheet.getRange(i, 1).getValue();
    let result;
    try {
      // 対象シートのA1セルの値を取得
      result = getA1Value(sheetName);
    } catch(error) {
      // エラーメッセージを作成
      result = error.name + ": " + error.message;
    } finally {
      // B列に結果を書き込む
      mainSheet.getRange(i, 2).setValue(result)
    }
  }
}

function getA1Value(sheetName) {
  const sheet = ss.getSheetByName(sheetName);
  return sheet.getRange("A1").getValue();
}

なお、tryブロック内やcatchブロック内にreturn文やthrow文が存在する場合でも、その直前にfinallyブロックの処理が実行されます。

throw文で独自の例外を発生させる

throw文を使うと、独自の例外を発生させることができます。
例として、秘密というシートを対象にした場合に、独自の例外が発生するようにしてみます。

「秘密」シートはA1セルに ZZZ と入力されている
「秘密」という名前のシートを作成する
メインシートのA列に「秘密」を追加した
メインシートのA列に「秘密」を追加する

これまでのスクリプトであれば、秘密シートのA1セルの値である「ZZZ」が記載されるはずですが、スクリプトを修正して、エラー「Error: 秘密です」が記載されるようにしてみます。

const ss = SpreadsheetApp.getActiveSpreadsheet();

function myFunction() {
  // ... 省略 ... //
}

function getA1Value(sheetName) {
  if (sheetName === "秘密") throw new Error("秘密です!");
  const sheet = ss.getSheetByName(sheetName);
  return sheet.getRange("A1").getValue();
}

myFunction関数を実行すると、下図のようになりました。

シート名に「秘密」を指定した行は、B列に独自のエラーの内容が転記された。
秘密 シートを指定すると、エラーが表示されるようになった。

このように、throw new Error("メッセージ")とすると、その時点で独自の例外を発生させ、catchブロックに制御を移すことができます。

throw文の詳細が知りたい場合は、こちらのドキュメントが参考になります。

HtmlServiceでGAS側のエラーを扱う

HtmlServiceで作成したウェブアプリ等からgoogle.script.runでGoogle Apps Scriptの関数を実行する際には、withFailureHandlerを使うことでGAS側で発生したエラーに対処することができます。

サンプルとして、シート名を入力してボタンを押すと、そのシートのA1セルの値が表示されるウェブアプリを作成しました。

HtmlServiceからGASを実行するサンプル。シートを入力するinputと、実行ボタン、結果の表示欄を備える

まずは、例外処理なしのスクリプトを作成しました。

<!DOCTYPE html>
<html>
  <head>
    <base target="_top">
  </head>
  <body>
    <input type="text" id="sheet-name-input">
    <button onclick="handleClickButton()">A1の値を取得</button>
    <p id="result-text"></p>

    <script>
      function handleClickButton() {
        const sheetName = document.getElementById("sheet-name-input").value;
        const resultText = document.getElementById("result-text");
        // GASのgetA1Value関数を実行
        google.script.run.withSuccessHandler((result) => {
          // GAS側の処理が正常終了したら、結果を画面に表示する
          resultText.textContent = "A1の値は " + result + " です。";
        }).getA1Value(sheetName);
      }
    </script>

  </body>
</html>

存在しないシート名「無いシート」を入力してボタンを押すと、エラーが発生し、結果を画面に表示する処理は実行されませんでした。

エラーが発生し、結果が表示されなかった。コンソールにはエラーの内容が表示された。
エラーが発生し途中で処理が中断された。コンソールにはエラーが出力された。

Html側のスクリプトにwithFailureHandler(…)を追加し、GAS側で例外が発生した場合にエラー内容を表示するようにしてみました。

<!-- ... 省略 ... -->

    <script>
      function handleClickButton() {
        const sheetName = document.getElementById("sheet-name-input").value;
        const resultText = document.getElementById("result-text");
        // GASのgetA1Value関数を実行
        google.script.run.withSuccessHandler((result) => {
          // GAS側の処理が正常終了したら、結果を画面に表示する
          resultText.textContent = "A1の値は " + result + " です。";
        }).withFailureHandler((error) => {
          // GAS側の処理がエラーになったら、エラー内容を画面に表示する
          resultText.textContent = error.message;
        }).getA1Value(sheetName);
      }
    </script>

  </body>
</html>

例外が発生した場合にも処理が停止せず、エラー内容が画面に表示されるようになりました。

例外が発生したが、エラー内容が結果欄に表示された。コンソールにはエラーは表示されない。

GAS側で独自の例外をthrowした場合も、ちゃんと取り扱うことができています。

例外が発生したが、エラー内容が結果欄に表示された。コンソールにはエラーは表示されない。

受け取ったエラーオブジェクトの他のプロパティも表示してみました。
(GAS側で発生したエラーを受け取ると、nameプロパティは「ScriptError」になるようです。)

// ... 省略 ... //
        google.script.run.withSuccessHandler((result) => {
          resultText.textContent = "A1の値は " + result + " です。";
        }).withFailureHandler((error) => {
          // GAS側の処理がエラーになったら、エラー内容を画面に表示する
          resultText.innerHTML = 
            `error => ${error}<br />` +
            `error.name => ${error.name}<br />` +
            `error.message => ${error.message}<br />` +
            `error.stack => ${error.stack}`;
        }).getA1Value(sheetName);
// ... 省略 ... //

TypeErrorの場合:

結果欄に下記が表示された。

error => ScriptError: TypeError: Cannot read property 'getRange' of null
error.name => ScriptError
error.message => TypeError: Cannot read property 'getRange' of null
error.stack => at getA1Value (コード:25)

サンプルスクリプトの独自のエラーの場合:

結果欄に下記が表示された。

error => ScriptError: Error: 秘密です!
error.name => ScriptError
error.message => Error: 秘密です!
error.stack => at getA1Value (コード:23)

google.script.runおよび、withSuccessHandler, withFailureHandlerについての詳細は、こちらのドキュメントが参考になります。

コメント

タイトルとURLをコピーしました