Search code examples
angularrxjs

Angular rxjs - Call API recursively till status is true


I have 4 API calls first is to authenticate and than to upload a file and check file status. Once file status is return true than call API 4. Issue is with the fileStatus, I need to check file status till it is returned true and than only move, however, it just checks once and move to the next switchMap. Below is the code -

  this.homeService.auth().pipe(
  switchMap(authToken => {
    return this.homeService.uploadFile(this.masterFile);
  }),
  // Check file status
  switchMap(fileId => {
    return this.homeService.fileStatus(fileId).pipe(
      expand(status => {
        if (status === true || retries >= 50) {
          return of(status);
        } else {
          retries++;
          return of(status).pipe(delay(2000), switchMap(() => this.homeService.fileStatus(fileId)));
        }
      }),
      takeWhile(status => status === false && retries < 50, true),
      catchError(error => throwError('Failed to confirm upload status after 50 retries'))
    );
  }),
  switchMap((status: boolean) => {
    if (status === true) {
      return this.processFurther();
    }
  })
).subscribe(response => {
  console.lot('All done');
}, error => {
  console.log('Error processing ', error);
});

Solution

  • Your logic seems a bit complicated. Here's a function that will retry a set number of times with a delay that you can just plug into your stream.

    function pollUntilResult<T>(
        fn: () => Observable<T>,
        matchFn: (value: T) => boolean, 
        retries: number, 
        delay: number = 0
      ): Observable<T> {
    
      return timer(0, delay).pipe(
        concatMap((_, index) => fn().pipe(map(value => ({ index, value }))) ),
        takeWhile(({ index, value }) => index < retries && !matchFn(value), true),
        last(),
        map(({ value }) => value)
      );
    }
    

    This function does the following:

    • Starts a timer that emits immediately, and then for successive emissions emits at the passed delay value.
    • Uses concatMap to call the observable function which it assumes only returns one result. It will emit the current try index and the resulting value as an object. Note that using switchMap could be a mistake if the delay time is too short.
    • The takeWhile operator will keep emitting values while bad results are being emitted and the number of tries + 1 is less than retries. Note that the true being passed to the second parameter means that the last, potentially passing emission, should be emitted.
    • The last operator ensures only the last emission is emitted to the subscriber.
    • When emitting, the map operator is used to remove the try index and only return the value of interest.

    Your observable stream should now look like this. (I don't know what was going on with the catchError/throwError business, but that made no sense to me, and since your last switchMap didn't return an observable for false results, I moved it in tehre).

    this.homeService.auth().pipe(
      switchMap(authToken => {
        return this.homeService.uploadFile(this.masterFile);
      }),
      // Check file status
      switchMap(fileId => 
        pollUntilResult(() => this.homeService.fileStatus(fileId), 50, 2000)
      ),
      switchMap((status: boolean) =>
        (status === true) ? this.processFurther() : throwError('Failed to confirm upload status after 50 retries')
      })
    )