Angular 2 Http RetryWhen

Я пытаюсь использовать retryWhen в вызовах HTTP.

Он отлично работает, когда вы пытаетесь использовать это так:

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));
      })

Он делает максимум 3 попытки, если получает ошибку тайм-аута.

Но всегда имейте в виду, что я хочу сделать это более абстрактным для использования в различных случаях использования, и для этого я должен проверить тип ошибки.

Повторно будут предприняты только технические ошибки.

Так что я пробовал это без успеха.

.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);
      }
    });
  })

Он останавливается с первой попытки и не выполняет ни Observable.timer(), ни Observable.throw().

Я почти уверен, что проблема в первом flatMap, я уже пытался использовать mergeMap, но безуспешно.

Заранее спасибо!


person Paulo GR    schedule 06.12.2016    source источник


Ответы (2)


В RxJS 5 flatMap() — это просто псевдоним mergeMap() :).

Проблема в том, как вы используете обратный вызов для оператора retryWhen(). Он вызывается только один раз, а затем каждый раз, когда поступает сигнал об ошибке, он передается в Observable, возвращаемый из этого обратного вызова.

Во втором примере вы возвращаете Observable из attempts.flatMap, а затем снова подписываетесь на этот обратный вызов с .zip(attempts, i => i). Но этот оператор zip никогда не вызывается, потому что он вызывается после того, как значение уже было использовано attempts.flatMap. Также именно поэтому Observable.range(1, 3) всегда начинается с начала.

Я знаю, это выглядит запутанно. Просто имейте в виду:

  • обратный вызов для retryWhen() вызывается только один раз.
  • обратный вызов для attempts.flatMap() вызывается каждый раз, когда возникает ошибка.

Поэтому вам просто нужно реструктурировать свой код, например, так:

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')
    );

Это выводит на консоль:

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' }

Посмотреть демонстрацию в реальном времени: https://jsbin.com/hobeda/3/edit?js,console

person martin    schedule 07.12.2016
comment
Привет, это работает довольно хорошо, было бы более элегантно, если бы мы могли использовать оператор диапазона и избавиться от переменной счетчика, но это работает очень хорошо, спасибо! - person Paulo GR; 08.12.2016

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

  var source = Observable.create(obs => {
  obs.next(1);
  obs.next(2);
  obs.error(new TechnicalError('error from source'));
})
  .retryWhen(error => {
    console.log("Error occured, retryWhen initialized");
    return error.zip(Observable.range(1, 4), (error, i) => {
      return {
        ErrorObj: error,
        RetryCount: i
      }
    })
      .map(obj => {
        if (error instanceof TechnicalError) {
          if (obj.RetryCount > 3)
            throw obj.ErrorObj;
          //Retry every one sec for 3 times
          console.log('Retry # ' + obj.RetryCount);
          return Observable.timer(obj.RetryCount * 1000);
        }
        else {
          throw obj.ErrorObj;
        }
      }).concatAll()
  })
  .subscribe(
  val => console.log(val),
  err => console.log('subscribe error', err),
  _ => console.log('complete')
  );

Идея состоит в том, чтобы разместить оператор диапазона там, где он будет инициализирован только один раз (обратный вызов retryWhen), таким образом, оператор zip объединит любую ошибку с новым номером диапазона и передаст это оператору карты для выполнения логики проверки ошибок.

person Zeyad    schedule 30.10.2017