akcasoy
akcasoy

Reputation: 7215

RxJS: How to define a (maximum) duration for an (optional) request?

I have this logic:

  public setXXXInLocalStorage(): Observable<boolean> {

    return this.appConfig.getEndpoint('xxx')
      .pipe(
        switchMap((endpoint: Endpoint) => this.http.get(endpoint.toUrl(), {headers})),
        map((body: any) => { localStorage.setItem('myItem', body.item); }),
        timeout(5000),
        map(() => true),
        catchError(() => of(true))
      );
  }

What i wanted to achieve is, that i want to wait for max. 5 seconds for my get request.

What is now happening is: timeout waits always 5 seconds, even when my get request returns success in 1 sec.

Thanks in advance..

Upvotes: 2

Views: 3581

Answers (2)

akcasoy
akcasoy

Reputation: 7215

i think this solved my problem: how to cancel RXJS subscribe after a while

From @Kroltan:

Apparently rxjs client keeps the observable running after receiving a value (presumably so you get notified of further updates). And since the Observable is never completed, Timeout will act after 30 (or whatever you pass as duration) seconds after receiving the data, causing the stream to fail.

To convert the "streaming" Observable into a single-event Observable, use the Take operator before timing out:

So i just needed to add a take(1) before my timeout.

public setXXXInLocalStorage(): Observable<boolean> {

    return this.appConfig.getEndpoint('xxx')
      .pipe(
        switchMap((endpoint: Endpoint) => this.http.get(endpoint.toUrl(), {headers})),
        tap((body: any) => { localStorage.setItem('myItem', body.item); }),
        take(1),
        timeout(5000),
        map(() => true),
        catchError(() => of(true))
      );
  }

Upvotes: 2

Tim Klein
Tim Klein

Reputation: 2758

When the timeout operator follows the map operator, the value returned from the callback to the map operator is important as it determines what value is passed to the timeout logic. Since there is no return value in your map operation, then the timeout operator is passed undefined when it evaluates the Observable emission from the source (the HTTP get).

Since an Observable of undefined never completes, then the timeout operator will throw an error always (after the time specified).

Instead of using map for the side-effect of setting a value in local storage, use tap which will just ferry the source observable on to the next operator in the pipe.

const endpoint = rxjs.of('blah');

function getRequest(httpObservable, name) {
  return endpoint.pipe(
    rxjs.operators.switchMap(() => httpObservable),
    rxjs.operators.tap((val) => { // Call `tap` instead of `map`
      console.log(`${name}: ${val}`);
    }),
    rxjs.operators.timeout(2000),
    rxjs.operators.map(() => true),
  ).subscribe(
    null,
    () => console.log(`${name} Errored!`),
    () => console.log(`${name} Complete!`)
  );
}

const http = rxjs.timer(500);
const slowHttp = rxjs.timer(4000);

getRequest(http, 'Quick HTTP');
getRequest(slowHttp, 'Slow HTTP');
<script src="https://unpkg.com/@reactivex/[email protected]/dist/global/rxjs.umd.js"></script>

Upvotes: 0

Related Questions