2

We have an rxjs subscription that's failing to close. I'm wondering if this is a bug in the rxjs library or if we're doing something wrong.

Our code looks like this:

  private loadGroups(): void {
    if (this.groupOptionsSubscription?.closed === false){
      this.groupOptionsSubscription.unsubscribe();
    }
    this.groupOptionsSubscription =
      this.lookupService
        .getGroupLookups(null, this.filter.companyId)
        .pipe(debounceTime(environment.filterDelay))
        .subscribe(
          (groups) => {
            this.groupOptions$.next(groups);

            setTimeout(() => {
              console.log(this.groupOptionsSubscription.closed);
            }, 5000);
        });
  }

In this method, there is a subscription called groupOptionsSubscription. It first checks if it's not closed (or exists). If it is not closed (and it exists), we unsubscribe (which closes it immediately). Then we assign it to a new subscription that comes from this.lookupService.getGroupLookups(). When the observable fires, the subscribe callback runs. Once the callback is finished, the subscription should close (right?). Most of the time it does, but sometimes it doesn't. I can test this by console logging the closed state of the subscription in a 5 second timeout. About 1 in 5 times, it shows that the subscription is not closed.

This method can be called more than once before the previous observables fire. For example, it can be called 3 times, and only after the third time will the subscription callback run, with the previous two network requests being canceled and the previous two subscriptions being (I guess) disposed. In other words, only the last one completes. I have verified that it is this last one which also (sometimes) fails to close.

Does anyone know why this might be happening? Is this a known bug with the rxjs library? Or does it have something to do with the way we're handling subscriptions? Thanks.

gib65
  • 1,709
  • 3
  • 24
  • 58

1 Answers1

1

Maybe there's something in getGroupLookups that's causing the observable to stay open? You could add a take(1) to the pipe, but here's the thing, rxjs already has mechanisms to manage subscriptions. So instead of trying to keep track if a subscription is open or closed, just use what rxjs already has.

readonly loadGroupsSubject = new Subject();
readonly groupOptions$ = this.loadGroupsSubject.pipe(
  switchMap(() => this.getGroupLookups(null, this.filter.companyId)),
  debounceTime(environment.filterDelay)
);
  1. switchMap will cancel the previous emission if it's still open and start a new one. No need to check if a subscription is closed.
  2. You can add a take(1) inside the switchMap if getGroupLookups really is staying open and is causing a problem.
  3. Add shareReplay(1) if you want to share the result with multiple subscribers and you want late subscribers to have access to the last result.
  4. You could create a function that calls loadGroupsSubject and returns a reference to groupOptions$, but there isn't much point.
Daniel Gimenez
  • 18,530
  • 3
  • 50
  • 70
  • Thanks for the example, Daniel. I'm a bit confused about the syntax. First, if `loadGroupSubject` is a Subject, `this.loadGroupsSubject()` doesn't work, so I dropped the brackets: `this.loadGroupsSubject...`. Second, you wrote `readonly groupOptions$ =`, but did you mean `this.groupOptionsSubscription =`? We use `groupOptions$` to emit the groups that return in the subscription callback (for that reason, I also preserved the subscription callback after the pipes). Those changes got it to compile, but the subscription callback never runs (though `loadGroupsSubject` does returns a subscription). – gib65 Nov 16 '21 at 05:20
  • 1. Yes, the parens was an oversight. 2. You don't need to create a group options subscription just to update a groupOptions$ subject, you're just creating extra work, whoever subscribes to groupOptions$ originally should continue to do the same and they will get group options whenever there is an update to loadGroupsSubject. – Daniel Gimenez Nov 17 '21 at 18:38