Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Angular 2 Http RetryWhen

I'm trying to use retryWhen in HTTP calls.

It works perfectly when try to use like this:

return this.http.get(`${environment.apiUrl}/track/${this.user.instance._id}/${this.currentPlayer.playlist.id}/next?s=${this.playerCounter}`, options)
      .timeout(500, new TimeoutError(`Timeout trying to get next track. [instanceId=${this.user.instance._id}]`))
      .retryWhen(attempts => {
        return Observable.range(1, 3).zip(attempts, i => i).flatMap(i => 3 === i ? Observable.throw(attempts) : Observable.timer(i * 1000));
      })

It makes a maximum of 3 tries if get a Timeout error.

But, always have a buuut, I want to make this more abstract to use on various use cases and for this, I have to check the type of the error.

Only TechnicalErros will be retried.

So I tried this without success.

.retryWhen(attempts => {
    return attempts.flatMap(error => {
      if(error instanceof TechnicalError) {
        return Observable.range(1, 3).zip(attempts, i => i).flatMap(i => 3 === i ? Observable.throw(attempts) : Observable.timer(i * 1000));
      } else {
        Observable.throw(error);
      }
    });
  })

It stops at first try and does not execute the Observable.timer(), neither the Observable.throw().

I have almost sure that the problem is about the first flatMap, I already tried to use mergeMap, without success.

Thanks in advance!

like image 955
Paulo GR Avatar asked Feb 05 '23 14:02

Paulo GR


1 Answers

In RxJS 5 flatMap() is just alias to mergeMap() :).

The problem is in the way you use the callback for retryWhen() operator. It's called just once and then every time an error signal arrives it's pushed to the Observable returned from this callback.

In your second example you're returning Observable from attempts.flatMap and then subscribing to it again that callback with .zip(attempts, i => i). But this zip operator is never called because it's called after the value has been already consumed by attempts.flatMap. Also this is why the Observable.range(1, 3) starts always from the beginning.

I know this looks confusing. Just be aware thet:

  • the callback for retryWhen() is called just once.
  • the callback for attempts.flatMap() is called every time an error arrives.

So you just need to restructure your code, for example like the following:

var source = Observable.create(obs => {
        obs.next(1);
        obs.next(2);
        obs.error(new TechnicalError('error from source'));
    })
    .retryWhen(attempts => {
        console.log('retryWhen callback');
        let count = 0;

        return attempts.flatMap(error => {
            if (error instanceof TechnicalError) {
                console.log(error);
                return ++count >= 3 ? Observable.throw(error) : Observable.timer(count * 1000);
            } else {
                return Observable.throw(error);
            }
        });
    })
    .subscribe(
        val => console.log(val),
        err => console.log('subscribe error', err),
        _ => console.log('complete')
    );

This prints to console:

1
2
retryWhen callback
TechnicalError { msg: 'error from source' }
1
2
TechnicalError { msg: 'error from source' }
1
2
TechnicalError { msg: 'error from source' }
subscribe error TechnicalError { msg: 'error from source' }

See live demo: https://jsbin.com/hobeda/3/edit?js,console

like image 81
martin Avatar answered Feb 21 '23 00:02

martin