1

What i'm trying to do is to intercept a response and if here is a certain value (CHALLENGE), make a call to an other API and retry it 3 times if needed, then, if success, reply the first call and return the result to the caller, otherwise throw an error

this is my code, but it always end with ErrorObservable.create..

intercept(
    request: HttpRequest<any>, 
    next: HttpHandler
  ): Observable<HttpEvent<any>> {
    return next.handle(request).switchMap((event: HttpEvent<any>) => {
      if (event instanceof HttpResponse && event.body.result === "CHALLENGE") {
        return this.http
          .post<JsonResponse>(this.urlToChallenge + event.body.data.id, {
            observe: "response",
          })
          .pipe(
            tap((result) => {
              if (result.data.status !== "WAITING") {
                throw "WAITING";
              }
              return event;
            })
          )
          .pipe(
            retryWhen((errors) => {
              return errors
                .pipe(delay(this.retry_interval))
                .pipe(take(this.retray_time));
            })
          )
          .pipe(
            concat(
              ErrorObservable.create(
                new HttpErrorResponse({
                  error: "error",
                  headers: null,
                  status: 103,
                  statusText: "Error",
                  url: "",
                })
              )
            )
          );
      }
      return of(event);
    });
  }
}
Andrea Scarafoni
  • 937
  • 2
  • 13
  • 26

1 Answers1

1

I'm not entirely sure if I will answer your question but here we go

My idea here is whenever we go inside the "CHALLALNGE" case to have a way to count how many times we have retried the request if the request succeeds you will receive the response from the request if it fails more than 3 times you will go in the catch statement and will have to decide how to proceed

intercept(
    request: HttpRequest<any>,
    next: HttpHandler
  ): Observable<HttpEvent<any>> {
    return next.handle(request).switchMap((event: HttpEvent<any>) => {
      if (event instanceof HttpResponse && event.body.result === "CHALLENGE") {
        return this.http
          .post<JsonResponse>(this.urlToChallenge + event.body.data.id, {
            observe: "response",
          })
          .pipe(
            tap((result) => {
              if (result.data.status !== "WAITING") {
                throw "WAITING";
              }
              return event;
            }),
           retryWhen((errprs) => {
               return errors.pipe(
                 concatMap((response, indexOfRequest) => {
                   console.log('Retry N:', indexOfRequest + 1);
                   // Will retry n times, if success is not recieve will throw error
                   if (indexOfRequest + 1 >= 3) {
                     throw {
                       message: 'DOESNT_PASS',
                     };
                   }
                   return of(response);
                 }),
                 delay(this.retry_interval)
               );
            })
          ),
          catchError((e) => {
              console.log(e, 'Your error will come here if the request the second request fails 3 times, decide what to do');
              return of(e);
         })
      }
      return of(event);
    });
  }
}

Because I'm not able to reproduce the whole example I have created a pseudo rxjs example that should give you a better idea about the approach that I'm suggesting, that look like so

// Mock of the reuqest of type "CHALLANGE"
let mainRequest$ = of({
  type: 'CHALLANGE',
});

// Mock of the second request that is suppoused to fail few times

// Try to increase this to 4 to see the case where you go in the error state
const throwNTimes = 3;
let counter = 0;
let challangeRequest$ = of(null).pipe(
  switchMap(() => {
    counter++;
    // While there are less than 2 requests throw error
    if (counter < throwNTimes) {
      throw 'something';
    }
    return of({ type: 'SUCCESS' });
  })
);

// This is the stream that should be put in your intercepotr for the "CHALLANGE" case
const chalalngeStream$ = challangeRequest$.pipe(
  retryWhen((d) => {
    return d.pipe(
      concatMap((response, indexOfRequest) => {
        console.log('Retry N:', indexOfRequest + 1);
        // Will retry n times, if success is not recieve will throw error
        if (indexOfRequest + 1 >= 3) {
          throw {
            message: 'DOESNT_PASS',
          };
        }
        return of(response);
      }),
      delay(1000)
    );
  })
);

// This is your pseudo interceptor
mainRequest$
  .pipe(
    switchMap((x) => {
      if (x.type === 'CHALLANGE') {
        // Here I'm using comibe latest so that you can have latter both the responses from the primary and sub requests if needed, without the need for further calls
        return combineLatest([of(x), chalalngeStream$]);
      }
      // This is the case for all non "CHALLANGE" requests
      return of(x);
    }),
    catchError((e) => {
      // If your sub request fails more than 3 times, you will recieve your error here
      console.log(e, 'Your error will come here, decide what to do');
      return of([e]);
    })
  )
  .subscribe(([initialResponse, responseFromChallangeRequest]: any) => {
    console.log(initialResponse, responseFromChallangeRequest);
  });

// Open the console in the bottom right to see results.

A working version of the pseudo example enter link description here

You can also check out this question How to create an RXjs RetryWhen with delay and limit on tries

Side note, it's better to have multiple functions in single pipe when possible, instead of wrapping each rxjs operator in separate pipe