RXJS retryWhen with Promise

retryWhen(
  receives: (errors: Observable) => Observable, 
  the: scheduler
): Observable

Задача: Повторить запрос к серверу, если запрос завершился с ошибкой. При повторении ошибки - пауза между запросами увеличивается, при достижении лимита повторов - запросы прекращаются.  

retryWhen - повторить наблюдаемую последовательность при возникновении ошибки, используя настраиваемый критерий.

import {timer, interval} from 'rxjs'
import {map, tap, retryWhen, delayWhen} from 'rxjs/operators'

// испускать значения каждую 1s
const source = interval(1000)

//
const example = source.pipe(
  map(_ => {
    if (_ > 5) {
      // вызываем исключение, которое
      // будет получено оператором retryWhen
      throw _
    }

    return _
  }),
  // перехват ошибки, пауза, повтор последовательности
  retryWhen(errors => errors.pipe(
    // вывод сообщения (tap не влияет на поток)
    tap(_ => console.log(`value ${_} was too high!`)),
    // пауза
    delayWhen(_ => timer(_ * 1000)) 
  ))
)

//
const sub = example.subscribe(console.log) 

При ошибке поток будет повторен, после паузы в 3 сек. В примере поток будет повторятся постоянно, без остановки:

import {defer} from 'rxjs'
import {delayWhen, tap, retryWhen, mapMerge} from 'rxjs/operators'
import axios from 'axios'

const params = {}
const request$ = defer(_ => {
  return axios.get('/some/url', params)
}).pipe(
  // при возникновении ошибки
  // повторяем последовательность снова
  retryWhen(errors => {
    return errors.pipe(
      tap(_ => console.log('request error...')),
      // выдерживаем паузу в 3 секунды
      delayWhen(_ => rx.timer(3000))
    )
}))

Для более правильной работы нам необходимо увеличивать интервал повторения запроса и лимит повторений, а также игнорируемые HTTP-статусы:

import {} from 'rxjs'
import {} from 'rxjs/operators'
import axios from 'axios'

const retryStrategy = function(opts){
  opts = opts || {}
  let maxRetry = opts.maxRetry || 3
  let scaling = opts.scaling || 3000
  let excluded = opts.excluded || []

  return function(attempts){
    return attempts.pipe(
      mergeMap((error, i) => {
        const retryAttempt = i + 1
        if (
          retryAttempt > maxRetry || 
          excluded.find(e => e === error.status )
        ) { 
          return _throw(error)
        }

        console.log(`attempt: ${retryAttempt}, retrying in: ${retryAttempt * scaling}ms`)
        return timer(retryAttempt * scaling)
      }),
      finalize(_ => console.log('we are done'))
    )
  }
}