[OutSystems]JavaScript要素で発生した例外の扱い

Reactive Web App/Mobileでは、Client Action内でJavaScript要素を配置することで、JavaScriptのコードを記述できる。
では、そのJavaScript要素内で例外が発生して、JavaScript要素外に波及したとき（JavaScript要素内で処理されなかったとき）どうなるかを確認してみる。

# 環境
Personal Environment(Version 11.17.0 (Build 36291))
Service Studio(Version 11.53.17)

# サンプル
Forgeコンポーネント: [HousesoftSampleReactive](https://www.outsystems.com/forge/component-overview/7086/housesoftsamplereactive)のV1.0.12。

モジュール > MainFlow > Exception Screenの「③JavaScript要素で発生した例外の処理」下にある各ボタン

# 結果のまとめ

- JavaScript要素内で発生した例外が未処理だと、ローコード部分（Action Flow）ではAll Exceptionsで処理することになる
- よって、JavaScriptで発生する例外処理をGlobal Exception Handlerに任せるのでなければJavaScript内で行うことを考える。その際、JavaScript APIのLogger.errorでService Centerのエラーログに記録できる
- JavaScriptから非同期のClient Actionを呼んだときは、その内部で例外が起きるとAll ExceptionsでもHandleできないため、thenの第2引数（onRejectedコールバック関数）またはcatchで適切に処理する

# 1. JavaScript要素で発生した例外をcatchしなかった場合
まず基本を確認すると、Client Action内にJavaScriptを書くには、Action Flow内に要素を配置する。
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/332987/9d48e506-ba17-b5aa-9705-006f2467bcb4.png)

配置した要素をダブルクリックするとエディタが開くので、JavaScriptのコードを記述する。

ここでは、JavaScript要素でcatchされない例外を発生させたいので、以下のように記述してみる。

``` js
throw("throw an exception from inside of JavaScript element: test");
```

このとき、ローコード部分では、All ExceptionsのException HandlerでHandleできる。

# 2. JavaScript要素で発生した例外をOutSystemsのエラーログに記録するには
OutSystemsにはエラーログの仕組みが組み込みで備わっている。JavaScriptで発生したエラーについても同じ仕組みで記録したい。

この目的には、[JavaScript API - Logger](https://success.outsystems.com/ja-jp/Documentation/11/Reference/OutSystems_APIs/JavaScript_API/Logger)のBuilt-in Functionであるerrorが使える。

この関数に渡したメッセージは、console.logとService Center両方に出力される。
例外をcatchしたら、Logger.errorを呼べば良い。

``` js
try {
    throw("throw an exception from inside of JavaScript element: test");
} catch (ex) {
    $public.Logger.error($public.ApplicationContext.getCurrentContext().moduleName,
                         "Logger.errorで出力", ex);
}
```

出力例
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/332987/698cec15-2a6d-8f30-6fc6-7e166b6253c1.png)

# 3. 非同期のJavaScriptから例外
JavaScript内で\\$resolve/$rejectという関数を呼んだ場合、そのClient Actionは非同期の扱いになる。
詳細は[Outsystemsで非同期処理を実装する方法(Promiseを使う方法)](https://qiita.com/jyunji_watanabe/items/a26c2b9356c249e79193)を参照。

ここでは、以下のようにJavaScript要素の直後にMessage要素、また同じAction Flow内に各種Exceptionに対するHandlerを配置し、①JavaScript要素後も処理が継続するか②throwした例外がローコードでHandleされるか　を確認する。
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/332987/889f32f6-6656-0b1c-fe4b-80c193fc9f6c.png)

## 3.1 非同期JavaScriptから例外をthrow
JavaScriptのコード。コード中に\\$resolve呼び出しがあるので非同期の扱いになるが、\\$resolveの手前に例外のthrowがあるので実行は例外で中断する。
``` js
$parameters.Out1 = "正常終了";
throw("throw an exception from inside of JavaScript element: test");
$resolve();
```

この場合、All ExceptionsのException Handlerで捕捉できる。

## 3.2 thenのコールバックから例外をthrowした場合
別の非同期Client ActionをJavaScriptで呼び（$actions.を経由）、そのコールバック（then関数の第1引数で指定）中でthrowを使って例外を発生させた場合。

以下のサンプルコードでは、thenの第2引数は実際には到達しない。
$actions.AsynchronousClientAction()で呼び出しているAsynchronousClientActionは、非同期のJavaScript要素を配置したClient Action呼び出し。

``` js
$actions.AsynchronousClientAction().then(function(result) {
  $parameters.Out1 = "正常終了";
  throw("throw an exception from inside of then callback: test");
  $resolve();
}, function (error) {
  throw("rethrow: " + error);
});

```

``` js:AsynchronousClientAction Action内のJavaScript
$parameters.Out1 = "正常終了";
$resolve();
```

この場合、ローコードのException Handlerには引っかからない。

## 3.3 非同期でreject()されるClient Action呼び出しをし、適切なエラー処理コールバックを提供しない場合
3.2と同じ方法で別のClient Actionを読んでいるが、こちらでは、呼び出したClient Action内のJavaScriptが\\$reject()呼び出しで終わっている。
\\$reject()は、非同期のJavaScriptが失敗で終わったことを示す呼び出し。本来であれば、then関数の第2引数に設定するコールバック関数か、thenにつなげるcatch関数に設定するコールバック関数で処理する。この例では、あえてどちらも設定せず、どうなるか確認している。

``` js
$actions.AsynchronousClientActionWithReject().then(function(result) {
  $parameters.Out1 = result;
  $resolve();
} );

```

``` js:AsynchronousClientActionWithReject Action内のJavaScript
$parameters.Out1 = "Rejected";
$reject();
```

結果としては、ローコード部分のException Handlerには引っかからなかった。

# 3.4 非同期でreject()されるClient Action呼び出しをし、thenの第2引数で例外を発生させる
今度は、thenの第2引数（onRejectedコールバック関数）を提供し、その中で例外を発生させている。

``` js
$actions.AsynchronousClientActionWithReject().then(function(result) {
  $parameters.Out1 = result;
  $resolve();
}, function (error) {
    throw("rethrow from the second callback of then: " + error);
});

```

同じく、ローコード部分のException Handlerには引っかからなかった。

# 3.5 非同期でreject()の代わりに例外throwで終わるClient Action呼び出しをし、thenの第2引数で例外を発生させる

``` js
$actions.AsynchronousClientActionWithException().then(function(result) {
  $parameters.Out1 = result;
  $resolve();
}, function (error) {
    throw("rethrow from the second callback of then: " + error);
});

```

``` js:AsynchronousClientActionWithException Action内のJavaScript
$parameters.Out1 = "正常終了";
throw("throw an exception from inside of asynchronous JavaScript in a Client Action: test");
$resolve();
```

このパターンも、ローコード部分のException Handlerには引っかからなかった。

## 確認結果から
以上の結果から、「非同期JavaScriptを呼ぶときは、thenの第2引数（onRejectedコールバック）かcatchを設定する」ことが必要（ローコード部分のException Handlerでは処理できないため）。

ドキュメント（[Call an Asynchronous Client Action](https://success.outsystems.com/Documentation/11/Extensibility_and_Integration/JavaScript/Extend_Your_Mobile_and_Reactive_Apps_Using_JavaScript/Defining_Asynchronous_JavaScript_Code#call-an-asynchronous-client-action)）に記載されていた以下の記述とも符合する。

> You should always include error handling code when calling asynchronous client actions in JavaScript using one of the two approaches presented above: "then(onFullfilled, onRejected)" or "catch(onRejected)".