How to delay retrying to send an HTTP request with RxJS5?

756 views Asked by At

I'm having problems with making an RxJS5 observable stream behave in a way that I want it to.

The stream is supposed to send an HTTP request to a website using axios, and if the response is an HTTP error (which axios coerces to a JavaScript error), the observable sequence should wait 10 milliseconds and then try resending the request (for some reason the website that I'm sending the request to doesn't like it when you retry sending the request immediately and keeps throwing errors, but mostly behaves fine with a 10 ms delay).

Rx.Observable
  .fromPromise(axios('http://example.com/12345'))
  .map(x => new Promise((resolve, reject) => {
    setTimeout(() => {
      resolve(x)
    }, 2000)
  }))
  .debounceTime(2000)
  .do(console.log)
  .retry(10)
  .subscribe(console.log, console.error)

I have an example on Codepen with a few changes, to make it more apparent, how the stream works: http://codepen.io/leakyabstractions/pen/pNmvyZ?editors=0010

I tried using .delay(), .debounceTime(), .timer(), .timeInterval() and .timeout() in the place of the .map() operator, but nothing (including .map()) works. What am i doing wrong?

2

There are 2 answers

0
Olaf Horstmann On BEST ANSWER

So basically what you are looking for is a "retry after 10ms, but only 10 times"? (this is what your retry(10) suggests. I think a sophisticated solution would include retryWhen here:

const mockedRestError$ = Rx.Observable.throw("http://example.com/12345");

// we'll start with an empty string, because otherwhise
// we could not log the "start..."
Rx.Observable.of("")
  .do(() => console.log("start..."))
  .switchMapTo(mockedRestError$)
  .retryWhen(tenTimesWithDelay)
  .subscribe(console.log, console.error, console.info); // is never called, because 


function tenTimesWithDelay(errors) {
  return errors
    .scan((count, err) => {
      ++count;
      // optionally to throw the error all the way down to the subscription
      // comment the throw out to have the stream simply complete
      if (count >= 10) {
        throw err;
      }
      return count;
    }, 0)
    .takeWhile(count => count < 10)
    .do(count => console.log(`Retry #${count} in 100ms...`))
    .delay(100);
}

Here is the code-pen: http://codepen.io/anon/pen/bBydwZ?editors=0010

Please also note, that I set the delay to 100ms instead of 10ms just so it shows a little cleaner in the console.

1
Reinis Riekstins On

olsn's answer worked, however I'd like to share another solution that I accidentally came up with, which to my mind is a bit more straight forward:

console.log('start')
Rx.Observable
// emit observable every 10 ms, change to a bigger number to observe results
.interval(10)
// start immediately
.startWith(0)
// do it 10 times
.take(10)
.do(x => console.log('emitting', x))
// for every observable emitted, do an HTTP request
.flatMap(() => new Promise((resolve, reject) => resolve('resolved promise')))
.first(x => !(x instanceof Error))
.subscribe(console.log, console.warn, () => console.info('done'))