Execute code once the meteor callback has run


#1

Following code and sencario. I’m calling a meteor method within a polymer function. I want to let this function return a value that’s coming from the callback. The code below won’t work since the callback runs after the return. Is there a way to wait for the callback and only then return res?

getUserImage: function(userId) {
	// res returns an id that i want to return
  Meteor.call('getUserImage', userId, (err,res) => res);
  
  // can't do something like this since it's a callback.
  // need a way to inject res back into this computed binding
  return res
}

https://jsfiddle.net/kwsn54mf/


#2

Meteor.call on the client is asynchronous. The meteor pattern is to use reactivity

getUserImage: function(userId) {
  Meteor.call('getUserImage', userId, (err,res) => Session.set('userImage', res) );
  return Session.get('userImage');
}

this is the simplest of examples. Normally you would use this in a helper on a template and instead of a global Session variable would use a template scoped ReactiveVar. Checkout this guide using ReactiveDict


#3

That won’t work in this case because this function runs specifically the code with this ID then moves on to the next ID.

I believe this would have to be resolved with promises or async instead. I’m trying to experiment with this right now but I’ve never used promises/async before. Any suggestions how to make it work with them?


#4

You could check out deanius:promise which adds some nice syntactic sugar to promisify method calls. Alternatively, you could roll your own - something like:

const callWithPromise = (method, myParameters) => {
  return new Promise((resolve, reject) => {
    Meteor.call(method, myParameters, (err, res) => {
      if (err) reject('Something went wrong');
      resolve(res);
    });
  });
}

(async function() {
  const myValue1 = await callWithPromise('myMethod1', someParameters);
  const myValue2 = await callWithPromise('myMethod2', myValue1);
})();

or that self-executing function could be:

async function doStuff() {
  const myValue1 = await callWithPromise('myMethod1', someParameters);
  const myValue2 = await callWithPromise('myMethod2', myValue1);
}

doStuff();

#5

I did try that, however whenever I put async function() then I get an error saying unexpected token function() or something like that :confused: Not really sure why that’s happening. Tried almost exactly that code as you posted with the deanius:promise package :frowning:


#6

async, await and Promise work in Meteor out of the box. You don’t need any package.
Can you paste your code and the error message ?


#7

Yes @diegoolivier - I don’t use deanius:promise myself.

@AndreasGalster : The code I put together above does work (I just tested it to make sure!).

What version of Meteor are you using? Do you have the ecmascript package?


#8

Hmm not sure where it broke. I must have had a syntax error somewhere. It is working. But there’s something I still don’t quite understand about promises/async/await.

I am running this promise now within a function and I want to have that function return the promise value. I thought it would work somehow like the example in the last 3 lines but I guess that’s not it.

  getUserImage: function(toUserId) {

    const callWithPromise = (method, myParameters) => {
      return new Promise((resolve, reject) => {
        Meteor.call(method, myParameters, (err, res) => {
          if (err) reject('Something went wrong');
          resolve(res);
        });
      });
    }

    async function doStuff() {
      const myValue1 = await callWithPromise('getUserImage', toUserId);
      return myValue1;
    }
  
    // I want getUserImage: function(toUserId) to return the ID.
    return doStuff().then( (res) => {
      return res;
    });  

  },

#9

Unfortunately, that’s not going to work.

Promises don’t actually make your code synchronous, they only give that illusion by requiring you to maintain a strict chain of Promises. Async/await makes that chain easy to do, but the moment you want to break out and return to “normal” code you have to get the final resolution out. The issue there is that all the things in that chain are still happening asynchronously and the only way to get that final result is in the .then() method - but you can’t just return it - that moment passed because you returned the doStuff Promise instead (and if you remove the return in front of doStuff, you’ll just return undefined).

You could run a callback in your then(), to invoke the next bit of code:

getUserImage: function(toUserId, callback) {
  ...
  doStuff().then( (res) => {
    callback(res);
  });
}

Or you could Promisify the next step as well.


#10

Ok … After hours of trying to get this working, I realized I can write this also with a very simple Polymer component…

If anyone ever needs it.

<dom-module id='fb-user-image'>
  <template>
    <img src='https://graph.facebook.com/[[facebookId]]/picture?type=square'>
  </template>
</dom-module>


<script>
  require('@polymer/polymer/polymer.html');

  Polymer({
    is: 'fb-user-image',
    properties: {
      toUserId: {
        type: String,
        observer: 'getFacebookUserId'
      }
    },
    getFacebookUserId: function() {
      Meteor.call('getUserImage', this.toUserId, (err,res) => {
          this.facebookId = res;
      });
    },
  });

</script>

Thanks a lot rob, I got quite the headache because of trying to figure out async properly but I definitely learned a lot about async & promises here :slight_smile: