Search code examples
javascriptgeolocationasync-awaitecmascript-2017

Is it possible to wrap function such as Geolocation.watchPosition() in a Promise?


I wonder if it's possible to wrap Geolocation.watchPosition() https://developer.mozilla.org/en-US/docs/Web/API/Geolocation/watchPosition in a Promise and use it with async/await idioms in a way it does it's work; constantly returns positions whenever a device's location changes and invokes succeeding functions.

// Example Class
class Geo {
  // Wrap in Promise
  getCurrentPosition(options) {
    return new Promise((resolve, reject) => {
      navigator.geolocation.getCurrentPosition(resolve, reject, options)
    })
  }

  // Wrap in Promise
  watchCurrentPosition(options) {
    return new Promise((resolve, reject) => {
      navigator.geolocation.watchPosition(resolve, reject, options)
    })
  }

  // Works well.
  async currentPosition() {
    try {
      let position = await this.getCurrentPosition()
      // do something with position.     
    }
    catch (error) {
      console.log(error)
    }
  }

  // Any way...?
  async watchPosition() {
    try {
      let position = await this.watchCurrentPosition()
      // do something with position whenever location changes. 
      // Invoking recursively do the job but doesn't feel right.
      watchPosition()
    }
    catch (error) {
      console.log(error)
    }
  }
}

Solution

  • Not yet.

    The pattern you're describing is an Observable - there isn't language support for it in Javascript, but it's coming.

    In ES2015 we got generators: function* & yield, which allowed Iterators - pulling each yield with a for...of loop.

    Generators also support push Observers, with var valToGet = yield foo; and generator.next(valToPush); syntax.

    Generators are synchronous - they're just passing a single thread back and forth.

    In ES2017 we got async & await - these use generators under the covers to convert each await in an async function into a yield new Promise(.... The async function becomes a iterator of promises.

    Ideally we would be able to do something like this:

    async watchPosition*() {
        try {
            while(this.enabled) {
                // Loop each time navigator.geolocation.watchPosition fires success
                const position = await this.watchCurrentPosition();
    
                // Pass back to the listener until generator.next
                const atDestination = yield position;
                if(atDestination)
                    break; // We're here, stop watching
            }
        }
        catch (error) {
            console.log(error)
        }
    }
    

    Unfortunately, async function* isn't supported yet - functions can be generators or async, but not both. There also isn't the nice for...of syntax like there is for iterators, just the clunky generator.next(pushValue), so consuming this hypothetical method is a little ugly:

    async listener() { 
        const generator = Geo.watchPosition();
        let item = await generator.next(false);
        while (!item.done) {
            // Update UI
            const atDestination = areWeThereYet(item.value);
            item = await generator.next(atDestination);
        }
    }
    

    So asynchronous iterators/observables are coming, but there's lots to sort out first.

    In the mean time there are some exceptional libraries that support observer patterns and are available now, such as RxJS.