この記事はアイスタイル Advent Calendar 2025 7日目の記事です。
はじめに
こんにちは、第1プロダクト開発本部のkuriyamayです。
アドベントカレンダーの時期がやってきたので書いていこうと思います。
去年の記事はこちらで、なんと6年連続の参加となりました。
今年も頑張って書いていきます。
去年の記事
本記事のRxJSはバージョン7.8.1を使用しています
RxJSを使ったリトライ処理
外部APIやDBへの接続で一時的なエラーが発生したとき、「少し待ってから同じリクエストをもう一度投げたい」という場面はよくありますよね。
RxJS にはそのようなケースを扱うためのリトライ系オペレーターが用意されているので、本記事ではこのリトライオペレーター を紹介していきます。
リトライオペレーター
retry は catchError と同じく、ストリームが error を流したときの振る舞いを定義する
エラーハンドリング系オペレーターのひとつです。
用意されているのは number 型を受け取り MonoTypeOperatorFunction<T> を返すメソッドと、RetryConfig 型を受け取り MonoTypeOperatorFunction<T> を返すメソッドの2つです。
retry(count?: number): MonoTypeOperatorFunction<T>
ソースObservableがerrorを流したときに、最大count回まで再購読(リトライ)する
一番シンプルな形の retry です。
-
countを省略した場合は 無制限に リトライし続けます(デフォルトはInfinity) - 各リトライではソースObservableを最初から購読し直すため、HTTPリクエストなどの副作用も毎回実行されます
- 指定回数を超えてもエラーが解消されない場合は、最後に発生したエラーがそのまま下流に流れます(
catchErrorなどでハンドリング可能)
source$.pipe(
retry(3), // 最大3回までリトライ
catchError((err) => of(/* フォールバック値 */)),
);
retry(config: RetryConfig): MonoTypeOperatorFunction<T>
RetryConfig を受け取るオーバーロードでは、
- リトライ回数
- リトライ間の待機時間(ディレイ)
- 成功時にリトライ回数をリセットするかどうか
といった挙動を細かく制御できます。
主なプロパティは次のとおりです。
-
count: 最大リトライ回数(省略時はInfinity) -
resetOnSuccess:-
trueの場合、一度成功したあとに再度エラーが出ても「リトライ回数カウンタ」をリセットする
-
-
delay:-
numberを渡すと、そのミリ秒だけ待ってから次のリトライを行う - 関数を渡すと、エラー内容やリトライ回数に応じて待機用の
Observableをカスタマイズできる
-
source$.pipe(
retry({
count: 3,
resetOnSuccess: true,
delay: (error, retryCount) => timer(1000 * retryCount), // リトライごとに待機時間を伸ばすなど
}),
);
数値指定の retry
まずは数値だけを渡すretryのパターンです。
import { catchError, of, retry } from 'rxjs';
// 例: 外部APIを叩くObservable
const fetchUser$ = getUserFromApi$(); // 実装は省略
fetchUser$.pipe(
retry(3), // エラーになったら最大3回まで再購読する
catchError((err) => {
// 3回リトライしてもダメだったときの処理
console.error('ユーザ情報の取得に失敗しました', err);
return of({ id: 0, name: 'guest' }); // フォールバック値
}),
);
ポイントは次のとおりです。
- 一時的なネットワークエラーなどであれば、
retry(3)だけでかなりカバーできます - 何回目のリトライかは意識しなくてよいので、「とりあえず3回まで」であればこれで十分
- 「エラーになったら即リトライ」なので、待機時間を入れたいときは
RetryConfig版を使います
RetryConfig を使った retry
次に、RetryConfig を使って「指数バックオフしながらリトライし、成功したらカウンタをリセットする」という例です。
import { catchError, of, retry, timer } from 'rxjs';
// 例: 外部APIを叩くObservable
const fetchUser$ = getUserFromApi$(); // 実装は省略
fetchUser$.pipe(
retry({
count: 3, // 最大3回までリトライする
resetOnSuccess: true,
delay: (error, retryCount) => {
// retryCount は 1, 2, 3, ... と増えていく
const delayMs = retryCount * 1000; // 1回目1秒、2回目2秒…のように伸ばす
return timer(delayMs);
},
}),
catchError((err) => {
console.error('リトライしてもユーザ情報の取得に失敗しました', err);
return of({ id: 0, name: 'guest' });
}),
);
この例では、次のような挙動になります。
- 1回目の失敗 → 1秒待ってリトライ
- 2回目の失敗 → 2秒待ってリトライ
- 3回目の失敗 → 3秒待ってリトライ
- それでもダメなら catchError に流れてフォールバック処理
resetOnSuccess: true にしているので、
- 一度でも正常に値が流れてきたら、その時点でリトライ回数はリセットされる
- 「たまに失敗するけど、基本は成功している」ようなストリームでも、ある特定のリクエストだけが延々とリトライされ続けることを防げる
というメリットがあります。
HTTPステータスコードでretryをコントロール
特に外部APIとの連携では、先方の一時障害やネットワーク断で 5xx やタイムアウトが出ることが珍しくないため、自動リトライを挟むことで自サービスのエラー率を大きく下げられることがあります。
retry の delay 関数の中でエラー内容を見て、リトライ対象かどうかを判定することで実現できます。
import { catchError, of, retry, timer } from 'rxjs';
import type { AxiosError } from 'axios';
// 例: Axios で外部APIを叩くObservable
const fetchUser$ = getUserFromApi$(); // 実装は省略(HttpService などを想定)
fetchUser$.pipe(
retry({
count: 3,
delay: (error: AxiosError, retryCount) => {
const status = error.response?.status ?? 0;
// 4xx はクライアントエラーとみなし、リトライしても成功が見込めないので即エラーにする
if (status >= 400 && status < 500) {
// delay 内で例外を投げると、その時点で retry 自体もエラー終了する
throw error;
}
// 5xx やネットワークエラー(ステータスが取れないとき)はリトライ対象とする
const delayMs = retryCount * 1000; // 1回目1秒、2回目2秒…と伸ばす
return timer(delayMs);
},
}),
catchError((err) => {
// リトライしてもダメだった場合の最終的なエラーハンドリング
console.error('ユーザ情報の取得に失敗しました', err);
return of({ id: 0, name: 'guest' });
}),
);
このパターンのポイントは次のとおりです。
-
delayの中でエラーオブジェクト(ここでは AxiosError)を受け取り、statusを見て分岐している -
4xx系(認可エラー、バリデーションエラーなど)はリトライしても意味がないので、その場でthrowしてretryを打ち切る -
5xxやネットワークエラー(タイムアウトなど)はリトライ対象とし、timerで待機時間を制御する - すべてのリトライが失敗した場合や
4xxで打ち切った場合は、最後にcatchErrorに流れてフォールバック処理を行う
このように、retry と RetryConfig を組み合わせると、「どのエラーはリトライしてよいのか」「どのエラーは即座に失敗させるべきか」をドメインルールに合わせて細かく表現できます。
最後に
RxJS を使った自動リトライ処理について、基本の retry(3) から RetryConfig による細かい制御、HTTPステータスコードによるリトライ可否の切り分けまで紹介しました。
これらを組み合わせることで、外部APIやDBなど「自分ではコントロールできない要因」によるエラー率を下げつつ、サービスの要件に合わせた振る舞いを表現できます。
まだ retry をあまり使っていなかった方も、ぜひ今回のパターンを取り入れてみてください。
引き続きAdvent Calendar 2025 をお楽しみください。