0

getCurrentPosition() is asynchronous, so how do I return a value from a helper?

Template.pos.helpers({
    'nearestLocation': function() {
        var location = undefined;

        if('geolocation' in navigator) {
            navigator.geolocation.getCurrentPosition(function(position) {

                location = Locations.findOne({
                    geolocation: {
                        $near: {
                            $geometry: {
                                type: 'Point',
                                coordinates:[153.0415850, -27.4477160]
                            }, 
                            $maxDistance: 500
                        }
                    }
                });

                console.log(location.name);

                return location.name;
            });
        }
    }
});

The find works correctly because the console does output the correct result. Am I missing something I should know about the way Meteor works?

Ian Jones
  • 1,369
  • 1
  • 10
  • 15

3 Answers3

1

You can't return a value directly from a function if the value is retrieved asynchronously. The function returns BEFORE the async value has been retrieved.

The usual way to solve this issue is to pass a callback into the function and then the host function calls the callback and passes it the value when it has been retrieved.

If in the infrastructure you are using, the nearestLocation function is required to be synchronous (e.g. it can't use a callback or return a promise that would later return the value), then you're just kind of out of luck because you can't return an async value from a synchronous interface in Javascript. It just can't be done.

The only possible work-around I know of is to somehow anticipate what value will be needed long before it is needed so you can retrieve the desired value asynchronous, store it away and then when the synchronous request is made for the value, it can just be returned because it has previously been retrieved.

This requires advanced knowledge of what will be requested and enough time in advance to actually retrieve it before it is needed.

jfriend00
  • 683,504
  • 96
  • 985
  • 979
  • Thanks for your answer! I'm a bit of a nub with JavaScript, but I understand the sync/async incompatibility here. This is in the context of Meteor.js so there's an added layer of magic happening which I wondered might have a solution. – Ian Jones Mar 15 '15 at 03:36
  • 1
    @IanJones - I don't know Meteor myself, but in searching, it looks like there are some answers that use a `session` and the `render` method [here](http://stackoverflow.com/questions/16632050/meteor-return-asynchronous-function-to-handlebar-template) and [here](http://stackoverflow.com/questions/24743402/how-to-get-an-async-data-in-a-function-with-meteor). Also a [whole module](https://github.com/hpx7/meteor-async-template-helpers/) about using async in helpers. – jfriend00 Mar 15 '15 at 03:43
1

Slightly nicer way than the callback pattern is to return a promise object from the function, which has a then() method which gets called when resolved, so you can chain your handler for the response onto the request like:

getCurrentPosition().then(function(result) {});

Promises are coming natively in ES6, but you use them today you'll have to either use a transpiler or a library specifically for promises like q, for instance.

liamness
  • 742
  • 3
  • 5
1

Just use a Session variable or other reactive data source to force the helper to rerun if/when the location is found:

Template.pos.helpers({
  nearestLocation: function() {
    var position = Session.get('position');

    // only bother doing the find if the position is set
    if (position) {
      // use position in the find
      var location = Locations.findOne(...);
      return location.name;
    }
  }
});

Template.post.rendered = function() {
  if (navigator.geolocation) {
    navigator.geolocation.getCurrentPosition(function(position) {
      // set the session variable when/if position is found
      Session.set('position', position);
    });
  }
};
David Weldon
  • 63,632
  • 11
  • 148
  • 146
  • Thanks for answering fully, helps to see an optimised solution. – Ian Jones Mar 15 '15 at 03:56
  • I'm happy to be of help. For more examples, also see [this related question](https://stackoverflow.com/questions/22147813/how-to-use-meteor-methods-inside-of-a-template-helper). It has to do with methods, but it's the same idea - async callback from a helper can be offloaded to created/rendered. – David Weldon Mar 15 '15 at 03:58