Start using async/await instead of promises and callbacks


#42

Yes - this sort of thing is supported:

Meteor.methods({
  async someMethod(x, y) {
    const a = await someAsyncFunction(x);
    return await someOtherAsyncFunction(a, y);
  }
});

#43

@robfallows have you actually done that? Based on what @ilacyero reports:

… I can’t use await inside a meteor method, because I should define the method as async and it generates error …


#44

Yes. It works exactly as it should when called from the client (the Promise is resolved before the result is sent back over the wire).

When I was playing with this a few months back I got unexpected results (the Promise itself) when calling an async method from within the same server code (not something I normally do).


#45

Hm, so given a methods-calling-methods situation, eventually some of those methods will be getting called form the server as well. And then it might (based on what I understand from your experience) cause issues, right?


#46

Perhaps. I found that behaviour, but I haven’t revisited those tests for quite a while, so it may be fixed now. I should probably take another look. I was putting a repro together for @benjamn, but got sidetracked and didn’t finish it. :disappointed:


#47

Hm, thank you, though, I guess I’ll need to find that out for myself sooner or later and I’ll sure do post back here if/when I do.


#48

Hmm, interestingly what I did which works is I just returned the promise in the method and then in the Meteor call all I did was await, seems a lot simpler than what you’re doing here and works for me but maybe there’s a reason why I shouldn’t do this?

I just trial and errored here, maybe this is not the right approach :smiley:


#49

Let’s say you have a call on the client like following

Meteor.call('myMethod', (err, res) => alert(res))

with an async function you can define the method like that:

const myPromise = () => new Promise((resolve, reject) => resolve('hi'))

Meteor.methods({
  myMethod: async () => {
    const msg = await myPromise()

    return msg
  }
})

This essentially allows to write your code in a “synchronous” style, while still staying async by awaiting promises.


#50

sorry @robfallows but it doesn’t. Async/Await stopped working a few months ago (this summer?). Now I have to use Promise.await().

It works in NPM modules, but as soon as the async is somewhere near client side code, it stops working.

I opened a few topics and filed a github issue, but I got no response.


#51

Do you have a repro? I’ve been using async/await on the client and server without issue (other than in-server method calls as mentioned). I’m currently on 1.4.2.


#52

The important thing to remember is that if you “promisify” a Meteor method, the Promise is resolved before the data is sent over the wire to the client. That means that you can, if you wish, continue to use the standard, callback form of Meteor.call and it will work as it always did.

However, you can “promisify” the call. I use something like this for a promised call:

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

and then inside an async function:

const result = await callwithPromise('someMethod', { some: parameter, someOther: parameter });

#53

Hmm, interestingly what I did which works is I just returned the promise in the method and then in the Meteor call all I did was await, seems a lot simpler than what you’re doing here and works for me but maybe there’s a reason why I shouldn’t do this?

I just trial and errored here, maybe this is not the right approach :smiley:


#54

@robfallows using your example just above, can you show an example of “chaining” promises too?

Chaining async calls using promises to me is the real value here.


#55

Hmm. Do you have a repo with this approach? I’ve just tried what (I think) you said and it doesn’t work in the way you explained.

  • The Promise object is not returned from the method. Instead, the Promise is resolved in the method and the result is returned. This makes sense, because if you think about it, a Promise may, for example, be waiting on an external REST endpoint. The external response will not be sent to your client for resolution.
  • If I do const result = await Meteor.call('someMethod'); I always get undefined: the method is called (so something happens on the server), but there is no Promise to await, so the call completes without waiting.

#56

Using await makes chaining Promises almost as simple as using sync-style coding on the server. In principle I would take something like that callWithPromise and just use multiple calls:

const thing = await callWithPromise('methodA'), {} );
const factor = await callWithPromise('methodB', { item: thing } );
const result = await callWithPromise('methodC', { item: thing, factor } );

The only “gotchas” you need to be aware of are:

  1. You can only await inside an async function. However, you can do funky things like Template.bob.onCreated(async function() {... which makes your entire onCreated async.
  2. There is still likely to be a resolution step if you want to get a result from an await chain out of your async function and back into your “normal” code. In Meteor you can do this easily by making use of ReactiveVars.

So, in Blaze, you could do something like:

Template.bob.onCreated(async function() {
  this.result = new ReactiveVar();
  const thing = await callWithPromise('methodA'), {} );
  const factor = await callWithPromise('methodB', { item: thing } );
  this.result.set(await callWithPromise('methodC', { item: thing, factor } ));
}

Template.bob.helpers({
  getResult() {
    return Template.instance().result.get();
  }
});

Avoiding callback hell. Meteor.call promise
#57

Thanks rob. But you can’t use the .then syntax somehow?

let calculate = function (value) {
    return new Promise((resolve, reject) => {
        setTimeout(() => {
            resolve(value + 1);
        }, 0);
    });
}; 

calculate(1)
    .then(calculate)
    .then(result => result + 1)
    .then(calculate)
    .then(verify);
 
function verify(result) {
    expect(result).toBe(5);
    done();
};

#58

Yes. They’re standard Promises (at least insofar as any of the plethora of Promises are standard).


#59

I tried this but I’m not getting the result from the client via console.log, the server is fine though.

export const useProfileAddress = new ValidatedMethod({
  name: 'cart.useProfileAddress',
  mixins: [CallPromiseMixin],
  validate: new SimpleSchema({
  }).validator(),
  run(args) {
    return (async function() {
      try {
        const storeId = await getClosestStore.callPromise()
        console.info('storeId', storeId) // This part returns something on the Server but it's undefined on the client.
      } catch (e) {
        console.log(e.message);
      }
    }())
  }
})

export const getClosestStore = new ValidatedMethod({
  name: 'store.getClosest',
  mixins: [CallPromiseMixin],
  validate: new SimpleSchema({
  }).validator(),
  run(args) {
    const store = Store.findOne() // Could it be this?
    return store._id
  }
})

Am I missing something? Or should I enclose all my method in Meteor.isServer?


#60

Try this:

export const useProfileAddress = new ValidatedMethod({
  name: 'cart.useProfileAddress',
  mixins: [CallPromiseMixin],
  validate: new SimpleSchema({
  }).validator(),
  async run(args) {
    try {
      const storeId = await getClosestStore.callPromise();
      console.info('storeId', storeId); // This part returns something on the Server but it's undefined on the client.
      return storeId;
    } catch (e) {
      console.log(e.message);
    }
  },
});

#61

Thank you @robfallows that lessened my lines of code but I’m still not getting the storeId from the browser console. So I’ve enclosed my code in Meteor.isServer to inhibit the error. Could it be because the method is trying to get it from minimongo and not from the Mongo since it was simulated on the client?

Particularly this line on my getClosestStore method?

const store = Store.findOne()
    return store._id