How to declare async function myFunction() inside Meteor methods so that front end can call it?

My question is related to how put the following doSomethingFirst async function inside of Meteor methods that front end can also call it?

server/main.js:

import Promise from 'bluebird';
const callAsync = Promise.promisify(Meteor.call);
Future = Npm.require('fibers/future');

async function doSomethingFirst() {
   const res0 = await callAsync('fetchFromServiceA');
   const res1 = await callAsync('writeOrUpdatedB', res0); 
}

Meteor.methods({

fetchFromServiceA: function () {
  var future = new Future();
  // my business logic
  return future.wait();
    },

writeOrUpdatedB: function () {
  var future = new Future();
  // my business logic
  return future.wait();
    },

Do you do inside of Meteor.methods:

async doSomethingFirst: function() {
   const res0 = await callAsync('fetchFromServiceA');
   const res1 = await callAsync('writeOrUpdatedB', res0); 
}
});

I have good news :slight_smile: smile: and bad news :disappointed:

The good news: my experimentation with Promises in Meteor methods shows:

  1. If you use the inbuilt ecmascript Promises (including async/await, they seem to play very nicely with Meteor.
  2. If you want to use Bluebird, and you promisify the async versions of any of Meteor’s sync-style (fiber) methods, along with any other async methods you’re using, then you can avoid that irksome fiber stuff and it works. The fundamental issue with Bluebird if you also need to use Meteor’s sync-style stuff “as-is” is that the resolve and reject callbacks need to be wrapped in fibers - and that’s a pain.

So, using the ecmascript Promises, you can, for example, define your code like:

async function doSomething() {
   const res0 = await somePromisedAsyncFunction();
   const res1 = await anotherPromisedAsyncFunction(res0);
   return res1;
}

Meteor.methods({
  doSomethingFirst() {
    return doSomething();
  },

  fetchFromServiceA() {
  // my business logic
    return new Promise(...);
  },

  writeOrUpdatedB(res) {
    // my business logic
    return new Promise(...);
  },
});

And they will all resolve as expected when called from the client.

The bad news: they don’t resolve as expected when called from the server, so this doesn’t work correctly:

async function doSomething() {
   const res0 = await callAsync('fetchFromServiceA');
   const res1 = await callAsync('writeOrUpdatedB', res0);
   return res1;
}

Meteor.methods({
  doSomethingFirst() {
    return doSomething();
  },

  fetchFromServiceA() {
  // my business logic
    return new Promise(...);
  },

  writeOrUpdatedB(res) {
    // my business logic
    return new Promise(...);
  },
});

What seems to happen is that instead of getting a result (resolved Promise), the actual Promise is returned as the result. I’ve not fully bottomed this out yet, but it looks like it may be a bug - perhaps @benjamn could comment? Note, I haven’t tried a server-to-server call, only a “within-server” call.

In any event, although methods are supposed to be callable “within-server”, the guide recommends using direct function calls (probably just for performance reasons).

Note that you should only use a Method in the case where some code needs to be callable from the client; if you just want to modularize code that is only going to be called from the server, use a regular JavaScript function, not a Method.

So, as long as you’re prepared to move method logic into separately callable functions for server use, you should be good to go (I haven’t got round to testing that either).

1 Like

The short answer is that you can’t use Fiber or Future on the client, so you should stick to Promise when writing methods/stubs.

If you return a Promise from a method implementation, the result should be delayed until the promise is resolved. If that isn’t happening for server-to-server calls, that’s a bug.

Have you seen this pull request? Would that be useful to you?

A small tip about async method syntax. Instead of writing a separate async function like

async function doSomething() {
   const res0 = await somePromisedAsyncFunction();
   const res1 = await anotherPromisedAsyncFunction(res0);
   return res1;
}

Meteor.methods({
  doSomethingFirst() {
    return doSomething();
  }
});

you should simply be able to write

Meteor.methods({
  async doSomethingFirst() {
    const res0 = await somePromisedAsyncFunction();
    const res1 = await anotherPromisedAsyncFunction(res0);
    return res1;
  }
});
5 Likes

Agreed.

That’s what I think I’m seeing.

I think that’s generally useful - it’s one of the areas where isomorphism currently breaks down. I’d be happy to go with Promises everywhere for the sake of wider ecosystem standardisation (although I do have a soft spot for fibers/futures).

:slaps head: Nice! Obvious when you point it out!

What about using RSVP instead of Bluebird? Can I then structure it as though it is ecmascript Promise server side calls?

Here is some code. I believe what you want is ‘await call normal’

import { Meteor } from 'meteor/meteor'

const slow_function = () => {
  for (let i = 1000000000; i >= 0; i--)
  if (i == 0) return 1
}

const normal = () => slow_function ()
const promise = () => new Promise ((resolve, reject) => resolve (slow_function ()))
const async = async () => slow_function ()

const call = method_name => new Promise ((resolve, reject) => {
  Meteor.call(method_name, (e, r) => resolve(r))
})

if (Meteor.isServer) Meteor.methods({ normal, promise, async })

if (Meteor.isClient)
{
  console.log("start")

  console.log ("normal function call", normal ())
  console.log ("promise function call", promise ())
  console.log ("async function call", async ())

  console.log ("incorrect normal method call", Meteor.call('normal'))
  console.log ("incorrect promise method call", Meteor.call('promise'))
  console.log ("incorrect async method call", Meteor.call('async'))

  Meteor.call ('normal', (e, r) => console.log("normal method call", r))
  Meteor.call ('promise', (e, r) => console.log("promise method call", r))
  Meteor.call ('async', (e, r) => console.log("async method call", r))

  console.log ('call normal', call ('normal'))
  console.log ('call async', call ('async'))
  console.log ('call promise', call ('promise'))

  call ('normal').then(w => console.log('call normal + then', w))
  call ('promise').then(w => console.log('call promise + then', w))
  call ('async').then(w => console.log('call async + then', w))

  const f = async () => {
    for (let t of ['normal', 'promise', 'async']) {
      const r = await call (t)
      console.log ("await call", t, r)
    }
  }
  f ()

}

Gives on Meteor 1.3.4.4

start
test.js:22 normal function call 1
test.js:23 promise function call Promise {[[PromiseStatus]]: "resolved", [[PromiseValue]]: 1}
test.js:24 async function call Promise {[[PromiseStatus]]: "pending", [[PromiseValue]]: undefined}
test.js:26 incorrect normal method call undefined
test.js:27 incorrect promise method call undefined
test.js:28 incorrect async method call undefined
test.js:34 call normal Promise {[[PromiseStatus]]: "pending", [[PromiseValue]]: undefined}
test.js:35 call async Promise {[[PromiseStatus]]: "pending", [[PromiseValue]]: undefined}
test.js:36 call promise Promise {[[PromiseStatus]]: "pending", [[PromiseValue]]: undefined}
test.js:30 normal method call 1
test.js:31 promise method call 1
test.js:32 async method call 1
test.js:38 call normal + then 1
test.js:39 call promise + then 1
test.js:40 call async + then 1
test.js:45 await call normal 1
test.js:45 await call promise 1
test.js:45 await call async 1

I am not sure I understand the purpose of making a method return a promise : as far as I can tell the return value of methods is undefined. The promise needs to be in the callback given to Meteor.call(method, callback) to transform it into an async function that can be wait on.

Hi Diego

You certain that the promise needs to be in the callback?

This article talks about it:

async function asyncFun () {
  var value = await Promise
    .resolve(1)
    .then(x => x * 3)
    .then(x => x + 5)
    .then(x => x / 2);
  return value;
}
asyncFun().then(x => console.log(`x: ${x}`));
// <- 'x: 4'

So it is returning a promise and then the result is printed?