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.
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.
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
.
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.
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With