RxJS の Operators (6) - Observable のエラーハンドリング

この記事は [bouzuya's RxJS Advent Calendar 2015](http://www.adventar.org/calendars/1200) の 8 日目かつ RxJS Advent Calendar 2015 の 8 日目です。

# はじめに

今日は [ReactiveX の Operators の Error Handling](http://reactivex.io/documentation/operators.html#error) について [RxJS の API ドキュメント](https://github.com/Reactive-Extensions/RxJS/blob/v4.0.7/doc/api/core/observable.md)やソースコードを見ていきます。

また [RxJS 4.0.7](https://github.com/Reactive-Extensions/RxJS/tree/v4.0.7) を対象にしています。

# Observable のエラーハンドリング

## `subscribe` (`Observer`) の `onError`

各種 Operator よりまず `subscribe` (`Observer`) の `onError` です。`Observable.throw` でエラーを流してみましょう。

```js
import { Observable } from 'rx';

Observable
  .throw(new Error('ERROR!!'))
  .subscribe(
    value => console.log(`onNext: ${value}`),
    error => console.log(`onError: ${error}`),
    () => console.log('onCompleted')
  );
// onError: Error: ERROR!!
```

`onError` に入ると `onCompleted` は流れないようです。

では、途中で `throw` したらどうでしょう。

```js
import { Observable } from 'rx';

Observable
  .from([1, 2, 3])
  .map(x => {
    if (x < 2) return x;
    throw new Error('x >= 2');
  })
  .subscribe(
    value => console.log(`onNext: ${value}`),
    error => console.log(`onError: ${error}`),
    () => console.log('onCompleted')
  );
// onNext: 1
// onError: Error: x >= 2
```

当然 `try catch` されて `onError` に流れてきます。

## `Observable.catch` / `Observable.prototype.catch`

- [ReactiveX - Catch operator](http://reactivex.io/documentation/operators/catch.html)
- [`Observable.catch` API Document](https://github.com/Reactive-Extensions/RxJS/blob/v4.0.7/doc/api/core/operators/catch.md)
- [`Observable.catch` Source Code](https://github.com/Reactive-Extensions/RxJS/blob/v4.0.7/src/core/linq/observable/catch.js)
- [`Observable.prototype.catch` API Document](https://github.com/Reactive-Extensions/RxJS/blob/v4.0.7/doc/api/core/operators/catchproto.md)
- [`Observable.prototype.catch` Source Code](https://github.com/Reactive-Extensions/RxJS/blob/v4.0.7/src/core/linq/observable/catchproto.js)

`onError` の際に代わりに流す `Observable` または `Observable` を返す関数を指定できます。

```js
import { Observable } from 'rx';

Observable
  .from([1, 2, 3])
  .map(x => {
    if (x < 2) return x;
    throw new Error('x >= 2');
  })
  .catch(error => {
    console.log(`catch: ${error}`);
    return Observable.just(666);
  })
  .subscribe(
    value => console.log(`onNext: ${value}`),
    error => console.log(`onError: ${error}`),
    () => console.log('onCompleted')
  );
// onNext: 1
// catch: Error: x >= 2
// onNext: 666
// onCompleted
```

`Observable.prototype.catch(handler)` は `Error` を受けて続く `observable` を返します。

エラーをさらに流す場合は `throw` するか `Observable.throw` です。おそらく `Observable.throw` が行儀が良いのでしょう。

```js
import { Observable } from 'rx';

Observable
  .from([1, 2, 3])
  .map(x => {
    if (x < 2) return x;
    throw new Error('x >= 2');
  })
  .catch(error => {
    console.log(`catch: ${error}`);
    return Observable.throw(error);
  })
  .subscribe(
    value => console.log(`onNext: ${value}`),
    error => console.log(`onError: ${error}`),
    () => console.log('onCompleted')
  );
// onNext: 1
// catch: Error: x >= 2
// onError: Error: x >= 2
```

`Observable.prototype.catch(observable)` は `onError` 時に代わりに流すものを指定するようですね。

```js
import { Observable } from 'rx';

Observable
  .from([1, 2, 3])
  .map(x => {
    if (x < 2) return x;
    throw new Error('x >= 2');
  })
  .catch(Observable.just(666))
  .subscribe(
    value => console.log(`onNext: ${value}`),
    error => console.log(`onError: ${error}`),
    () => console.log('onCompleted')
  );
// onNext: 1
// onNext: 666
// onCompleted
```

どちらの場合でも、元の `Observable` のエラーを無視して流し続けるのはちょっと難しそうですね。

## `Observable.prototype.retry`

- [ReactiveX - Retry operator](http://reactivex.io/documentation/operators/retry.html)
- [`Observable.prototype.retry` API Document](https://github.com/Reactive-Extensions/RxJS/blob/v4.0.7/doc/api/core/operators/retry.md)
- [`Observable.prototype.retry` Source Code](https://github.com/Reactive-Extensions/RxJS/blob/v4.0.7/src/core/linq/observable/retry.js)

`onError` の際に指定した回数だけリトライします。

```js
import { Observable } from 'rx';

const maxRetryCount = 4;
let retryCount = 0;
Observable
  .from([1, 2, 3])
  .map(x => {
    console.log(`retryCount: ${retryCount}`);
    if (x >= 2 && retryCount < 2) {
      retryCount += 1;
      throw new Error('retryCount < 2');
    }
    return x;
  })
  .retry(maxRetryCount)
  .subscribe(
    value => console.log(`onNext: ${value}`),
    error => console.log(`onError: ${error}`),
    () => console.log('onCompleted')
  );
// retryCount: 0
// onNext: 1
// retryCount: 0
// retryCount: 1
// onNext: 1
// retryCount: 1
// retryCount: 2
// onNext: 1
// retryCount: 2
// onNext: 2
// retryCount: 2
// onNext: 3
// onCompleted
```

途中の `onNext` が流れる点、`onError` 以外であれば無視される点が注意点ですかね……。

# おわりに

今日は `Observable` のエラーハンドリングを見ました。

これで本当にエラーハンドリングに十分なのか不安です。