rxjstimeoutretrywhen

RXJS: adaptative timeout


I am using RXJS 6 and i trying to do a retry with adaptative timeout (i try first time with 1s timeout, if not successful i retry but with 2s timeout, etc...)

It look like the timeout value is not updated on retry, i am newbie on RxJS on i cannot find the problem.

Here the code snippet:

import { zip, timer, range, of, pipe } from 'rxjs';
import {  tap, retry, concatMap, timeout, catchError, delay, retryWhen } from 'rxjs/operators';

let timeoutFactor = 1;
let timeoutInit = 2000;
let timeoutValue = timeoutInit;

// simulate request
function makeRequest(timeToDelay) {  
  return of('Request Complete! '+timeToDelay).pipe(
    tap( () => console.log(`doing request (${timeToDelay}ms) with timeout of ${timeoutValue}`)),
    delay(timeToDelay));
}


const obs$ = of(4000, 3000, 2000)
  .pipe( 
    concatMap(duration => makeRequest(duration).pipe(

      timeout(timeoutValue),
      retryWhen(error => zip(error, range(1,3).pipe(
        tap( i  => {
            console.log("set timeout on " + (i*1000) + " ms")
            setTimeoutFactor(i);
          })
      )))    
    ))
  );

  obs$.subscribe(
    val => console.log(val),
    err => console.log(`err: ${err}` ),
    () => console.log(`complete` ));

  function setTimeoutFactor(value){
    timeoutValue = timeoutInit*value;
  }

But my result is not realy good:

doing request (4000ms) with timeout of 2000
set timeout on 1000 ms
set timeout on 2000 ms
set timeout on 3000 ms
doing request (4000ms) with timeout of 6000
doing request (4000ms) with timeout of 6000
doing request (4000ms) with timeout of 6000
doing request (3000ms) with timeout of 6000
timeout: 6000
Request Complete! 3000
doing request (2000ms) with timeout of 6000
timeout: 6000
Request Complete! 2000
complete

any help ?


Solution

  • Retrying after a delay of your choice cannot be that complicated. For example the following (tested working code) sample retries after exponential delay (1s, 4s, 9s, 16s, 25s ...):

    // source observable simply throws error, which is ideal for testing retry concept
    const source = throwError(new Error('Oops..!'));
    
    const smartSource = source.pipe(retryWhen(error => {
    return error.pipe(mergeMap((e, i) => {
          const delayFactor = i * i *  1000;
          console.log(`Retrying after ${delayFactor / 1000} seconds`);
          return of('Retry signal').pipe(delay(delayFactor));
    }));
    }));
    
    smartSource.subscribe(x => console.log('result...'));
    

    If you want to change the delay logic, it is as simple as modifying the delayFactor according to your needs. For example if you want to retry every second:

    const delayFactor = i * 1000;