[GAS, JavaScript] try-catch と Error のこと

# これは何？

非エンジニア向けにGoogleAppsScript(GAS)レクチャーを行なっています。プログラムが全くの初めての人を対象にしています。このレクチャーを卒業し、基本的なことが理解できるようになった人が「その次に知っておいたほうがいいこと」の一つにErrorへの対応があるなと思いました。これを説明してみよう、という記事。


# 対象者

* GoogleAppsScriptおよびJavaScriptでの基本構文は知っているレベル
* 具体的には [ケーススタディでしっかり身につく! Google Apps Script超入門](https://www.amazon.co.jp/dp/4297126273) を修了しているレベル


# エラーとは？

初学者にとってエラーは可能なら見たくないし、メッセージも英語だから読みたくないし、もうイヤだ。という心境になりますよね？私はなりました。

だけど、エラーは「ここがおかしいよ」って教えてくれているので、それを読み解くことでエラーを解消することができます。

例えば下記の関数を実行するとエラーが出ます。

```js
function myFunction() {
  console.log(abc);
}
```

```text
ReferenceError: abc is not defined
```

ここでは `ReferenceError` というエラーが発生しています。[Error \- JavaScript \| MDN](https://developer.mozilla.org/ja/docs/Web/JavaScript/Reference/Global_Objects/Error) によると`ReferenceError` は

```
不正な参照から参照先の値を取得した時に発生するエラーを表すインスタンスを生成します。
```

とのことです。

上記のコードでは、宣言されていない `abc` という変数をいきなり呼び出していることで「不正な参照から参照先の値を取得」しようとしているために `ReferenceError` が起こっています。

(一言で「エラー」と言ってしまいますが、実際は「どういうときにどんなエラーが出るのか」は決められています。`SyntaxError` `TypeError` あたりはよく出会うエラーかと思います)

[Error \- JavaScript \| MDN](https://developer.mozilla.org/ja/docs/Web/JavaScript/Reference/Global_Objects/Error) によると

```
実行時エラーが発生すると、新しい Error オブジェクトが生成されスローされます。
```

とのことです。Errorオブジェクトとは？スローとは？ってなるかもしれませんがここでは「エラーが起こると `Errorオブジェクト` という何かができる」くらいに考えておきます。


# エラーが発生するとプログラムが終了する

あたりまえ、って思うかもしれませんが、エラーが発生するとプログラムはそこで終了します。例えば下記のコードでは、`console.log(abc);` でエラーになるので、それ以降の行は実行されていません。

```js
function myFunction() {
  console.log('あいうえお');
  console.log(abc);
  console.log('かきくけこ');
}
```

GASで実行したときのログ↓

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/8612/5fcf5aed-f189-121b-b4c2-70720e97e83c.png)

プログラムが「異常終了」しています。(最後まで実行されていない)


# エラーが発生したときに気づきたい

「エラーが発生したことを通知(メールを送るとか、チャットツールに送信するとか)したい」というニーズがあります。めっちゃあります。

「実行したいときに自分で実行するタイプのプログラム」であれば、その処理が終わるまで見ていればエラーが出たことにすぐ気づけると思います。

しかしながら「毎日午前0時に定期実行される」のようなプログラム(GoogleAppsScriptでトリガーで定期実行しているようなプログラム)の場合、途中でエラーになっても気づけません。[^1]

[^1]: 正確にはGASでトリガーを設定するときに、「どのタイミングでエラーがあったことの通知をするか」を選ぶことができます

私がよく使うのは「定期実行しているプログラムがエラーになったら、Slackのエラー通知チャネルに通知する」ということをよくやります。これを実装するときに `try-catch`を使います。


# try-catch の使い方

構文としては下記のようになります。


```js
try {
  // エラーが起こるかもしれない処理
} catch(e) {
  // エラーが起こったときの処理
}
```

実際にエラーを起こしてみましょう。

```js
function myFunction() {
  try {
    console.log('あいうえお');
    console.log(abc);
    console.log('かきくけこ');

  } catch (e) { // e は好きな変数名を付けてOKです。eでもerrでもerrorでも。
    console.log(e);
  }
}
```

GASのログ↓
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/8612/acfce6db-85db-ad1d-cfbf-284344632099.png)



処理の順番としては下記のようになります。

1. `console.log('あいうえお');` が実行される
1. `const a = b;` でエラーが発生する
1. エラーが`catch`される
1. `console.log('エラー発生！', e);` が実行される
1. プログラムが **「正常終了」** する

これの活用例としては

```js
  } catch (e) {
    // エラーが発生したことをSlackに通知する処理を書く
  }
```

をしておけば、エラーが出た時点でSlack通知がされ、異常が起こっていることにすぐ気付けます。


また、注目すべき点としては  ログでは「実行完了」が出力されています。つまりこれは **エラーにはなっていない。つまりプログラムは正常に終了している」** ということです。

`try-catch`がない場合は`Error`が発生するとプログラムが「異常終了」しますが、`Error`を`catch`して、`console.log(e);` をするというプログラムが実行され、全体としては「正常にプログラムが実行された」という結果になります。


# Errorオブジェクトを自分で作ることもできます

上の例だと`console.log(abc);` が実行されたところで `ReferenceError` が発生しています。
自分でErrorを作ることもできます。

```js
function myFunction() {
  try {
    console.log('あいうえお');
    throw new Error('自分で作ったエラー');
    console.log('かきくけこ');

  } catch (e) {
    console.log(e);
  }
}
```

```plaintext
あいうえお
[Error: 自分で作ったエラー]
```

ここで「スロー (`throw`)」が出てきましたね。

```plaintext:再掲
実行時エラーが発生すると、新しい Error オブジェクトが生成されスローされます。
```

今回は自分で実行時エラーを自分で発生させ、新しいErrorオブジェクトを生成(`new`)し、throwしたわけです。


# try-catch の実践例

私はGASをよく使うのでGASの例を書きます。

プログラムの本体は`main()`関数です。ただ、この関数を実行したときに何らかの`Error`が発生したときに、すぐSlackに通知したい。

```js
/**
 * トリガーで実行するのはこの関数 (もっといい命名あったら教えてほしい)
 */
function do_main() {
  try {
    main(); // この関数でErrorが発生したらcatchしたい。
  } catch (e) {
    const message = `[プロジェクト名] でエラーです。
エラー内容: ${e.stack}`;

    alert(message); // Slackに通知するalert関数は別に定義しあるとする
    throw new Error(message); // これをしておくことで実行ログにエラーとして記録される
  }
}


/**
 * こちらが処理の本体
 */
function main() {
  // スプレッドシートから値をとったり、別サービスのAPIにリクエストしたりする
  // 実行したときにエラーが出る可能性がある
  
  console.log(abc); // ここでエラーを発生させてみる
}
```

Slackに通知される message は下記のようになります。

```plaintext
[プロジェクト名] でエラーです。
エラー内容: ReferenceError: abc is not defined
    at main (無題:25:15)
    at do_main (無題:6:5)
    at __GS_INTERNAL_top_function_call__.gs:1:8
```

こうしておくことで、main()が実行されてErrorがあったらSlackに通知されることですぐに気づくことができます。

補足情報としては、

```js
    const message = `[プロジェクト名] でエラーです。
エラー内容: ${e.stack}`;
```

のように `e.stack` としています。出力結果をみるとわかるように、こうすることでエラーがどこで起こったかの履歴を出力してくれます。↓ この部分。

```
    at main (無題:25:15)
    at do_main (無題:6:5)
    at __GS_INTERNAL_top_function_call__.gs:1:8
```

`__GS_INTERNAL_top_function_call__` が実行されて、
`do_main` が実行されて、
`main` でエラーが起こってるよ、ということですね。(エラーが起こってる箇所が上になってる)


# 正常終了させるのか、異常終了させるのか

下記の処理については意見が分かれそうかなと思いました。

```js
  } catch (e) {
    const message = `[プロジェクト名] でエラーです。
エラー内容: ${e.stack}`

    alert(message);
    throw new Error(message);
  }
```

私は↑こうしておくことで「Slackに通知したうえで、プログラムを異常終了させる」をしています。
最後の一文

```js
    throw new Error(message);
```

を書くことで「わざわざErrorを発生させて異常終了させてる」んですよね。



この理由ですが、GASの実行ログを見る画面では下記のように表示されます。

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/8612/c9036c13-2f75-c46c-7b68-c29ee7cb6735.png)

上のログは `throw new Error(message);` を消したときのログ。ステータスが「完了」になっています。
下のログはこの一文があるときのログ。こちらは「失敗しました」です。

失敗を失敗として記録しておいたほうが後からわかりやすい、という理由からです。


# 最後に

`JavaScript try-catch` で検索すると、関連する記事がいっぱいできてきます。

* `try-catch` の中に `try-catch` を書くことができる
* `try-catch-finally` を使うことで `try`または`catch`が終了する直前に処理をさせる事ができる

のような情報がありますので、是非調べてみてください！ (私は`finally`を使ったことはないです...)