How to get results from async function in a helper?


#1

I have this helper which calls ImageResolver.js to resize images, but it’s not working because resolver return async. I think I need to use a reactiveVar to maintain the template instance or something, but nothing I’ve tried has worked.

  getImage: function(url) {
    resolver.resolve(url, function(result) {
      if (result) {
        console.log(result.image);
        return result.image;
      } 
    });
  }
});

#2

https://atmospherejs.com/simple/reactive-method :wink:


#3

My function is not a server-side call. it runs in the client. I’d have cross-site scripting issues if I tried to port it to a meteor.method


#4

Oh, ok… so you could declare ReactiveVar in Template onCreated(onRendered) and then store the method result in it. It should work. What is the problem with this approach?


#5

I’m not sure but I just get errors

Error: Exception in template helper:
TypeError: Cannot read property 'set' of undefined

Template.discover.onCreated(function(){
  this.imageResolverRequests = new ReactiveVar;


});
Template.discover.helpers({

  getImage: function(url) {

    console.log("page:" + url);
    url = Helpers.addHttp(url);

    var result;
    this.imageResolverRequests.set(result.image);

    resolver.resolve(url, function(result) {
      if (result) {
        console.log(result.image);

        return Template.instance().imageResolverRequests.get();
      } else {
        console.log("No image found");
        return '';
      }
    });
  },

How to get the result after I store it? should I put the whole helper function in the reactivevar?


#6
Template.instance().imageResolverRequests.set(result.image);

In a helper, this is the data context, not the template instance


#7

Thanks, I changed this and tried this code but I get new errors

Template.discover.helpers({

  getImage: function(url) {

    console.log("page:" + url);
    url = Helpers.addHttp(url);

    var result;
    Template.instance().imageResolverRequests.set(result);

    resolver.resolve(url, function(result) {
      if (result) {
        console.log(result.image);

        var res = Template.instance().imageResolverRequests.get();
        return res.image;
      } else {
        console.log("No image found");
        return '';
      }
    });
  },
Error: Cannot read property 'imageResolverRequests' of null
TypeError: Cannot read property 'imageResolverRequests' of null
    at helpers.js?0a6261ab33124edc5a3198af4665deef5c0f08d9:21

#8

should be this.imageResolverRequests = new ReactiveVar();

(with parentheses - and maybe an initial value): http://docs.meteor.com/#/full/reactivevar


#9
Template.discover.helpers({

  getImage: function(url) {

    console.log("page:" + url);
    url = Helpers.addHttp(url);

    //var result;
    

    resolver.resolve(url, function(result) {
      if (result) {
        console.log(result.image);
       /*//// set the reactiveVar in the callback (when it arrives) */
       Template.instance().imageResolverRequests.set(result.image);
      } else {
        console.log("No image found");
        return '';
      }
    });
    // return the image into your template (when it gets updated)
    return Template.instance().imageResolverRequests.get();
},

#10

Thanks guys, but still no cigar :frowning:

Error: Cannot read property 'imageResolverRequests' of null
TypeError: Cannot read property 'imageResolverRequests' of null

using this declaration now

Template.discover.onCreated(function() {
  this.imageResolverRequests = new ReactiveVar();

});

#11

Do I need to use Promise to make this work?


#12

null is returned by Template.instance() when it can’t determine the current helper (amongst other reasons), so I wonder if this might work:

Template.discover.helpers({

  getImage: function(url) {
    var instance = Template.instance();
    console.log("page:" + url);
    url = Helpers.addHttp(url);

    //var result;
    

    resolver.resolve(url, function(result) {
      if (result) {
        console.log(result.image);
       /*//// set the reactiveVar in the callback (when it arrives) */
       instance.imageResolverRequests.set(result.image);
      } else {
        console.log("No image found");
        return '';
      }
    });
    // return the image into your template (when it gets updated)
    return instance.imageResolverRequests.get();
},

#13

as well @robfallows’ suggestion, above

it might also be worth declaring the reactive var with null value

Template.discover.onCreated(function() {
  this.imageResolverRequests = new ReactiveVar(null);
});

#14

HI @robfallows
Thats interesting! On the positive side, it’s the first time I got results back! But on the negative side, it seems to spawn an infinite loop! It’s still running after a full 5 minutes. I’m only trying passing it 12 urls.

Ah maybe after I get the result I should destroy the reactVar? I’ll try that.


#15

this should be in helper

The whole setting and checking should be part of something else - event handler or autorun in onCreated.

And yes - setting reactive var in async callback and still using it in same helpers as .get() is effective infinite loop


#16

Good news that the image grabber works.

the infite loop is a tracker issue, everytime it changes, it calls the helper, and the helper is calling the list of urls, and the url is pulled and the helper is called and …

need to break out the logic somewhere


#17

Correct - and that’s how I’ve done it before (in onCreated)! My brain must be elsewhere today.


#18

@robfallows’s .set and .get are both inside the helper

  getImage: function(url) {
    var instance = Template.instance();
    //console.log("page:" + url);
    url = Helpers.addHttp(url);

    //var result;

    resolver.resolve(url, function(result) {
      if (result) {
        //console.log(result.image);
        /*//// set the reactiveVar in the callback (when it arrives) */
        instance.imageResolverRequests.set(result.image);
      } else {
        console.log("No image found");
        return '';
      }
    });
    // return the image into your template (when it gets updated)
    return instance.imageResolverRequests.get();
  },

but yeah, now how to deal with the infinite loop. I thought this might work, but when I try it, i get no images at all

    var imgUrl = instance.imageResolverRequests.get();
    instance.imageResolverRequests.set(undefined);
    return imgUrl;


#19

@shock is correct - putting the set in the helper will cause reactive runaway ™. The setting needs to be outside of the helper - in onCreated is a good place, or in an event if you’re kicking things off from a click.


#20

it’s the whole of this

var instance = Template.instance();
//console.log("page:" + url);
url = Helpers.addHttp(url);

//var result;

resolver.resolve(url, function(result) {
  if (result) {
    //console.log(result.image);
    /*//// set the reactiveVar in the callback (when it arrives) */
    instance.imageResolverRequests.set(result.image);
  } else {
    console.log("No image found");
    return '';
  }
});

perhaps in onRendered