プログラマが想定していなかった例外でプロセスが落ちる問題にPromiseで対応する

Node.jsのコード中で`throw`された場合、try-catchを入れないと`uncaughtException`となる。
また、EventEmitterで`on('error')`を定義していない場合も`emit('error')`の内容が`throw`されるため、同様に`uncaughtException`になる。
`uncaughtException`が発生するとNode.jsはプロセスを終了する。

プログラマが想定していなかった例外でプロセスを終了させないために、どういう手段がとれるか考えてみる。

## 問題点

### 1. イベントハンドラで`on('error')`を入れ忘れる

EventEmitterではプログラマが`on('error')`を定義しない場合、例外を`throw`する。
（[Node.jsでは`on('error')`を書きましょうと説明している](https://nodejs.org/api/events.html#events_error_events)）
`throw`された例外は、try-catchされていなければトップレベルに到達し、Node.jsのプロセスが終了してしまう。

この例外を検知する方法として、前述した`uncaughtException`を利用することができる。
しかし、例外を発生させたイベントハンドラを設定しているEventEmitterを再利用することは推奨されない。

イベントハンドラ内で例外が発生した場合に無限ループ処理になってしまうことを防ぐため、という説明がある。
以下Node.jsのドキュメントより抜粋。

> Exceptions thrown from within the event handler will not be caught. Instead the process will exit with a non zero exit code and the stack trace will be printed. This is to avoid infinite recursion.

#### なぜイベントハンドラで発生した例外から回復するのがよくないか

確かにイベントハンドラ内で、不用意にcatchした例外から回復させるのは安全でない。
下記は超雑な例だが、想定外の状態のイベントを無理やり再実行するような書き方もできるといえばできる。
ただ、想定外の例外が発生したためにイベントの状態が予測できず、回復が難しいと思われる。

```javascript
const EventEmitter = require('events');

const emitter = new EventEmitter();

emitter.isStarted = false;
emitter.on('event', (val) => {
  if (!emitter.isStarted) {
    console.log('emitter was not started.');
    emitter.isStarted = true;
    
    emitter.emit('error', new Error('error event')); // same as throw Error
  }
  console.log('emitter was started. nothing to do.');
});

try {
  emitter.emit('event', 'error!');
} catch (e) {
  console.log('caught error! : ' + e);
  emitter.emit('event', 'error!'); // error won't thrown.
}
```

### 2. ふつうにtry-catchを入れ忘れる

イベントハンドラとは関係なくtry-catchを入れ忘れるというしょうもないミスで、プログラマが想定していなかった`throw`が実はあり、ある時突然`throw`された例外でプロセスがダウンするという状況は起こり得る。
依存しているライブラリ内で入れ忘れがあったりすると、全然想定していなかったプロセスダウンになりそう。

```javascript
// 依存ライブラリの関数を呼び出し
somthing.call(); // 関数下でthrow Errorがあると、try-catchしていなければ当然プロセスダウンする
```

## Promiseでtry-catch入れ忘れ問題に対処する

Promise内で`throw`されたエラーは`catch()`に渡される。
また、非同期処理でのエラーは`reject()`に渡す、とされている。

> The executor is expected to initiate some asynchronous work and then, once that completes, call either the resolve or reject function to resolve the promise's final value or else reject it if an error occurred.

https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Promise

```javascript
function errFunc() {
  throw new Error('wooooooooo!!');
};

const mypromise = new Promise((resolve, reject) => {
  // 非同期処理のエラー
  reject(err);

  errFunc(); // throw new Error('err');
});

mypromise.then(value => {
    console.log('promise is resolved. : ' + value);
  })
  .catch(err => {
    console.log(err);
  });
```

`catch()`がない場合は、Promise内で発生した`throw`や`reject()`は何のエラー後処理も行われない。`uncaughtException`としても扱われない。
`on('unhandledRejection')`で、`catch()`で処理されなかったエラーを取得することができる。
非同期処理はPromiseを使って書く、という方針にすると、try-catch入れ忘れで想定外の`uncaughtException`発生からのプロセスダウンの流れは避けられる。

※ `on('error')`のように非同期でエラーイベントを取得した場合は`reject()`し、`throw`されるものと一緒に`catch()`で処理することができる。

## catch()書き忘れ問題

try-catch書き忘れ問題についてはPromiseで解決できる。では、Promiseで`catch()`しなかったらどうなるかという、これもまたしょうもない問題を思いつく…。
`catch()`を書き忘れて処理されていない例外は、`on('unhandledRejection')`で拾うことができる。
`on('unhandledRejection')`で、発生した`catch()`されない例外についてログを出力しておくことによって、とりあえず未処理の例外があるということには気付ける。

```javascript
process.on('unhandledRejection', (reason, promise) => {
  // handle rejection
});

function errFunc() {
  throw new Error('wooooooooo!!');
};

const mypromise = new Promise((resolve, reject) => {
  // 非同期処理のエラー
  reject(err);

  errFunc(); // throw new Error('err');
});

mypromise.then(value => {
    console.log('promise is resolved. : ' + value);
  })
```

## まとめ

* しょうもないが、try-catch書き忘れや`on('error')`書き忘れでNode.jsのプロセスが落ちることがある
* Promiseを使うと上記のような書き忘れで不用意にプロセスが落ちることを避けることができる