Issei's Blog

【RxJS入門】エラー時に一定時間後リトライする(retry、retryWhen)

こんにちはムラびとです。 最近スクレイピングの依頼が増えたのですがその最中でソケットが上がったり、リクエスト数が多すぎて一時停止することがよくあります。 そこでエラー時にその処理を繰り返せるretry、retryWhen関数の登場です。 そこで今回は、Observableがエラーを返してきた時にある回数まで一定時間後にリトライ、一定回数後にエラーを返すプログラムを作っていきます。

  • retry関数とは
  • retryWhen関数を使って一定時間後リトライ
  • リクエストエラーのステータスコードによる分岐

retry関数

RxJSは非同期処理に強く、エラー処理が簡単に書くことができます。 例えば、リクエストを送る時、サーバーの都合でリクエストが失敗することはよくあります。 そんな時に最初から処理を繰り返すことができるようにするのがretry関数です。

import { from,of,throwError } from 'rxjs'; 
import { flatMap ,retry} from 'rxjs/operators';


const source = from([2,4,6,8])
  .pipe(
    flatMap(n=>(n>4)?throwError(n):of(n)),
    retry(5)
  )

source.subscribe(
  x => console.log(x),
  err=>console.error(err)
);

などのように使い、リトライの最大回数を設定することができます。

retryWhen関数を使って一定時間後リトライ

リクエストを送って失敗したら数秒後にリトライして欲しい、という時にはretryWhen関数を使います。 retryWhen関数を使うことで、送られたエラーを変換してretryすることできます。 これを使うと例えばエラーが起きたら10秒後にリトライさせることができます。

import { throwError, of } from 'rxjs'
import { delay, retryWhen, take, tap, concat, flatMap } from 'rxjs/operators'
export const retryWithDelay = (ms: number, count: number) =>
  retryWhen(errors => errors.pipe(
    flatMap((err, i) =>
      (i >= count))) ?
        throwError(err) :
        of(err)
    ),
    tap(err => console.log(`err:${JSON.stringify(err)}, retrying...`)),
    delay(ms),
    concat(throwError('retried too much...'))
  ))

使い方としては以下のようにします。


const source = from([2,4,6,8])
  .pipe(
    flatMap(n=>(n>4)?throwError(n):of(n)),
    retryWithDelay(1000,10)// 1s間隔で10回までリトライ
  )

またこれを使うとスクレイピングなどでアクセス制限をされないようランダムにウェイトをかけることもできます。


export const retryWithRandomDelay = (min: number, max: number, count: number) =>
  retryWithDelay(Math.random() * (max - min) + min, count)

リクエストエラーのステータスコードによる分岐

リクエストで、404(Not Found)エラーがきた場合、何度送っても意味がありません。そこで、エラーのステータスコードによってリトライするかどうかを判断するようにプログラムを書いていきます。

export const retryWithDelay = (ms: number, count: number, excludedError: (string)[] = []) =>
  retryWhen(errors => errors.pipe(
    flatMap((err, i) =>
      (i >= count || excludedError.find(e => e == (err.statusCode || ''))) ?
        throwError(err) :
        of(err)
    ),
    tap(err => console.log(`err:${JSON.stringify(err)}, retrying...`)),
    delay(ms),
    concat(throwError('retried too much...'))
  ))

exculdedErrorにステータスコードの配列を入れてそれらのステータスコードが来たらリトライをせずエラーをそのまま返す、ということができます。

終わりに

ここまで見ていただきまことにありがとうございます。 他にもいくつかRxJSについて記事を書いていますのでよろしければご参照ください。

出典

公式サイト