Wait for Promise to resolve before result is used in switchMap

419 Views Asked by At

I have the following code which works intermittently. Sometimes the getCurrentPosition promise does not resolve in time, and undefined gets passed to the first switchMap in my pipe, which breaks everything of course.

Location Service:

    // Get user location
    public getUserLocation(): Observable<LocationModel> {
        const options = {
            enableHighAccuracy: true,
            timeout: environment.userLocationTimeout
        };

        return from(Geolocation.getCurrentPosition(options)).pipe(
            map((position) => {
                const location = {
                    lat: position.coords.latitude,
                    lng: position.coords.longitude
                };

                return location;
            })
        );
    }

Subscriptions Page:

    // Get Subscriptions
    private getSubscriptions(): void {
        this.locationService
            .getUserLocation()
            .pipe(
                switchMap((location: LocationModel) => this.locationAddressService.getAddress(location)),
                switchMap((address: AddressModel) => this.subscriptionsService.getSubscriptions(address)),
                takeUntil(this.destroy$)
            )
            .subscribe((subscriptions) =>
                this.zone.run(() => {
                    this.subscriptions = subscriptions;
                })
            );
    }

I am expecting the from() to only return the LocationModel when the promise is resolved, but that doesn't seem to be what's happening. Am I using it incorrectly? How can I ensure that there is a LocationModel ready for the switchMap?

We are trying to stay away from promises as much as possible as they mess with our error logging, so are trying to use observables wherever we can.

Additionally, assuming the location is returned on time, I need to set the UI element inside the NgZone, if I don't then it takes a long time to update the UI after the subscriptions have been returned. I don't think this is the same issue however.

1

There are 1 best solutions below

0
Mrk Sef On

From what you've written, I have to assume your promise is resolving with undefined. The "right" way to fix this is to figure out the conditions which cause Geolocation.getCurrentPosition to resolve undefined and guard against those:

someCondition().then(_ =>
  Geolocation.getCurrentPosition(options)
)

or with RxJS:

defer(() => someCondition()).pipe(
  switchMap(_ => Geolocation.getCurrentPosition(options)),
  map((position) => ({
    lat: position.coords.latitude,
    lng: position.coords.longitude
  })
)

If that's difficult to investigate, then you can just retry until you get a value. As an aside, it's almost always a mistake to use RxJS::from with a promise. You can use defer instead to unify promises with RxJS's lazy semantics.

defer(() => Geolocation.getCurrentPosition(options)).pipe(
  tap(v => {
    if(v == null) {
      throw "error";
    }
  ),
  retry(_ => timer(500)),
  map((position) => ({
    lat: position.coords.latitude,
    lng: position.coords.longitude
  })
)

If retry works, I would upgrade it to a version that doesn't retry forever. I'll leave that exercise up to you.