Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

How to cancel a pending request in an inner HTTP observable with RXJS?

Cancelling a pending HTTP request when changing the page

We have an Angular service that has an expensive HTTP query that 3 different consumers can access. Each consumer can modify this query at any point, make a new request, and all other consumers must be updated with the new data.

Because HTTP subscriptions close immediately on completion, we used the inner observable pattern with behavior subjects to keep the consumers connected (see below).

The problem then, is when the user changes the page, there's no way for the current pending HTTP response to get cancelled.

Normally I don't think it would be too much of an issue to throw away an HTTP request in the background...but beyond it being an expensive operation, I have discovered if the pending response is indeed resolved after the user has returned back to the page, it'll update the consumers with data for an older query.

No bueno.

Service call

private dataSubject = new BehaviorSubject<MyData>(...);
public data$ = this.dataSubject.asObservable();
...

getData(): Observable<MyData> {
  if (this.dataSubject)
    return this.data$;
  } else {
    const http$ = this.http.post(...))
      .pipe(map(response => response as MyData),
        takeUntil(this.unsubscribe$)); // see tearDown() below
    http$.subscribe(
      (availableDevices: MyData) => {
        this.dataSubject.next(availableDevices);
      }
    );
    return this.data$;
  }
}

I attempted to create a tear down method in the service that each consumer calls during it's ngDestroy(), but it did not work unless I completed the stream. But at that point, I wasn't unable to restart the stream again when the user returns to the page.

tearDown(): void {
  this.unsubscribe$.next();
  this.unsubscribe$.complete();
  // this.dataSubject.next(null);
  // this.dataSubject.complete(); -- breaks
}

I'm by no mean an RXJS expert, so feel free to point out if my overall design is wrong. I have a nagging suspicion that I should be using switchMap() or share to prevent two consumers making the same request; but because this observable pattern is semi-hot, I'm not sure what the right course of action is. Let alone when it comes to cancelling it.

Any and all help would be appreciated.

like image 567
Robert Lee Avatar asked Mar 04 '23 13:03

Robert Lee


2 Answers

The easiest way to cancel a http call is to unsubscribe from the subscription.

const subscription: Subscription = this.http.post(...).subscribe(...);
subscription.unsubscribe();

Your teardown method is useful, and generally used as garbage collection and to cancel any pending rxjs observables. You would call it in a service/component/directive ngOnDestroy hook, so to make sure no observables remain in memory.

But if I remember correctly, angular handles http observables, so you don't need to handle them. Generally speaking you only need to take care of observables generated by yourself, like your BehaviorSubject.

like image 185
bodorgergely Avatar answered Apr 27 '23 12:04

bodorgergely


I solved that using Promises (async functions) with AbortSignal / AbortController, because in my case the http request was not cancelled although the subject was cancelled. Maybe I made a mistake or it would work now. However, Promises with AbortController do the job too.

See here how to use AbortController. However, there might be a problem with browser compatibillity when using AbortController.

You can also convert observables into promises with .toPromise() so that should not be an issue.

like image 34
Jeremy Benks Avatar answered Apr 27 '23 12:04

Jeremy Benks