概要
非同期処理において最も難しいのは、
**「エラー」「キャンセル」「リトライ」**という“制御不能な例外的状態”の取り扱いである。
Rxではこれらを**「設計されたストリーム制御の一部」**として構築できる。
本稿では、非同期におけるトラブル制御をRxの機能でどのように安全かつ柔軟に実現するかを、設計レベルで解説する。
1. なぜキャンセル・エラー処理は複雑化するのか?
従来の非同期処理(Promise, callback)では以下の問題が頻発する:
- リクエスト競合による無駄なIO
- 古い結果によるUIの汚染
- ネットワーク失敗時の再試行設計の煩雑さ
- 例外処理のネスト地獄
2. Rxにおけるキャンセル:unsubscribe()
const obs = interval(1000)
const sub = obs.subscribe(console.log)
setTimeout(() => sub.unsubscribe(), 3000)
- 
unsubscribe()により ストリームの発行元からの切断が可能
- UIやユーザー操作など、途中で止めたい処理に最適
3. switchMapによる自動キャンセル
search$
  .pipe(
    debounceTime(300),
    switchMap(query => fetchResults(query)) // 前のリクエストは自動キャンセル
  )
  .subscribe(render)
- ユーザー入力に対するAPIリクエストなど、最新以外を無視したい場合に効果的
- 
switchMapは「構造としてのキャンセル」
4. エラーハンドリング:catchError
ajax.getJSON('/api/data')
  .pipe(
    catchError(err => {
      console.error(err)
      return of([]) // エラー時は空配列を返す
    })
  )
  .subscribe(handleResult)
- 
try/catchではなく、ストリーム内でエラーも合成
- 「失敗したらこうする」も構造化できる
5. リトライ:retry, retryWhen
ajax.getJSON('/api/data')
  .pipe(retry(3)) // 最大3回まで自動リトライ
  .subscribe(handleResult)
ajax.getJSON('/api/data')
  .pipe(
    retryWhen(errors =>
      errors.pipe(
        tap(() => console.log("Retrying...")),
        delay(2000)
      )
    )
  )
  .subscribe(handleResult)
- 
retryWhenにより リトライタイミング・回数・間隔を完全制御可能
- ネットワークエラー・一時的なAPI障害の対応に有効
6. finalize:完了/キャンセル後の後処理
fetchData$
  .pipe(
    tap(() => isLoading$.next(true)),
    finalize(() => isLoading$.next(false))
  )
  .subscribe(handleData)
- 成功でもエラーでも、最終処理を共通で定義
- 
finallyのRxバージョン
実務でのパターンまとめ
| パターン | 演算子 | 説明 | 
|---|---|---|
| キャンセル | unsubscribe()/switchMap | 明示的または自動で処理を中断する | 
| エラーハンドリング | catchError() | エラーを補足してストリームを継続させる | 
| リトライ | retry(n)/retryWhen() | 回数やタイミングを制御した再実行 | 
| 共通後処理 | finalize() | 成功・失敗を問わず後処理を一括で行う | 
よくある誤解と対策
❌ ストリームでキャンセルやエラー処理までやるのは複雑
→ ✅ No。複雑な非同期を、構造として記述できるからこそ、保守性が上がる
❌ retryは自動で何度も走って危険
→ ✅ 制御できる。retryWhen で回数・条件・間隔を設計に落とし込める
❌ エラー処理がストリームに混ざると読みにくい
→ ✅ catchError は副作用を外に出す構造。設計次第で読みやすくなる(関数化・分離など)
結語
Rxは、エラーやキャンセル、リトライといった“例外的制御”を、設計として組み込むことができる数少ないパラダイムである。
- switchMapによる構造的キャンセル
- catchErrorでの明示的な失敗処理
- retryWhenによる戦略的な再試行
- finalizeによる収束
リアクティブな制御構造とは、
“混沌とした非同期の分岐や失敗を、秩序ある構文と流れに還元するための設計哲学である。”