1

I'm implementing an Angular service which calls a server for data. In every call I need to pass a token which lasts about a minute and then I need to map the response to get an specific field. So if my main call fails I need to call again for a token, wait for the response and then retry my first call, is there an easy way of doing this? Here are my two approaches (neither of them work propertly):

return this.http.post(url,
  firstCallText(this.token), { 
  responseType: 'text',
  headers
 })
  .pipe(
    map((xmlString: string) => {
      let asJson = this.xmlStringToJson(xmlString);
      return asJson["soap:Envelope"]["soap:Body"]["Response"]["#text"];
    }),
    catchError(async err=>{
      await this.http.post(url,
      getToken(),
      { 
        responseType: 'text',
        headers
      }).pipe(map((xmlString: string) => {
        let asJson = this.xmlStringToJson(xmlString);
        this.token = asJson["soap:Envelope"]["soap:Body"]["Response"]["Token"]["#text"];
      })).toPromise()
      return EMPTY
    }),
    retry()
  )

This first method fails because the retry() gets called before the new token is received. My second approach:

return this.http.post(url,
  firstCallText(this.token), { 
  responseType: 'text',
  headers
 })
  .pipe(
    map((xmlString: string) => {
      let asJson = this.xmlStringToJson(xmlString);
      return asJson["soap:Envelope"]["soap:Body"]["Response"]["#text"];
    }),
    retryWhen((errors) =>{
       this.http.post(url,
        getToken(),
        { 
          responseType: 'text',
          headers
        }).pipe(map((xmlString: string) => {
          let asJson = this.xmlStringToJson(xmlString);
          this.token = asJson["soap:Envelope"]["soap:Body"]["Response"]["Token"]["#text"];
        })).toPromise()
        return EMPTY          
    })
  )

The second one doesn´t retry correctly, I don't want to set a delay because the token call might be shorter or longer.

  • Your first approach might work if you drop the async/toPromise and simply return the http observable. – BizzyBob May 01 '22 at 14:05

1 Answers1

1

The issue that you are facing here is that the retry mimics the source without it's errors, where you actually want to handle the error itself.

So my suggestion is to extend your catchError opertaor in such a manner that it by itself handled the "failing" case and resumes the operation, for fetching data from the server.

Here is a pseudo code solution

// Answer for rxjs retryWhen promise is resolved  :https://stackoverflow.com/questions/72061841/rxjs-retrywhen-promise-is-resolved
const {interval, of, catchError} = rxjs;
const { switchMap, tap } = rxjs.operators;

// Start of Mock for your backednd
// requestToBeValidatedWithToken will either return success meassage whenver the token is valid or throw an error when the token has expired
let token = 0;
const requestToBeValidatedWithToken = () => {
  if (token < 0) {
    throw 'Expired token';
  }
  return of('Validated request suceeds');
};
// this mocks the refresh token logic
const fetchNewToken = () => {
  token = 3;
  return of('This refreshes the token');
};
// Timer that will invalidate your token
interval(1000).subscribe(() => {
  token--;
  if (token < 0) {
    console.log('BE message: Token has expired');
  }
});
// End of Mock for your backednd

// Start of rxjs pseudo code
// This will mock your base request stream, imaginge as the request is made each seconds
const methodInYourService = () => {
  const httpCall = interval(1000).pipe(
    switchMap(() => {
      return requestToBeValidatedWithToken();
    }),
    catchError((e) => {
      // This checks makes sure that only the "Expired token" case is handled so that you dont end in infinite loop
      if (e === 'Expired token') {
        console.log('Fire refresh token request', e);
        return fetchNewToken().pipe(
          tap(() => console.log('save your token as you recieve it')),
          switchMap(() => httpCall))
      }
      return e;
    })
  );
  return httpCall;
};

// This is the code inside your component, e.g. the place where you subscribe for the data
methodInYourService().subscribe(
  (x) => {
    console.log(x, 'fin');
  },
  (e) => {
    console.log('Will never come here, as we are handling the error');
  }
);
<script src="https://unpkg.com/rxjs@^7/dist/bundles/rxjs.umd.min.js"></script>

You can find working pseudo code solution here Live example

  • 1
    Thanks for the answer, although the of operator is now deprecated, I switched that to scheduled() as the [deprecation notes](https://rxjs.dev/deprecations/scheduler-argument) says. – Álvaro Fernández May 04 '22 at 11:25