Meteor: Calling an asynchronous function inside a Meteor.method and returning the result

Solution 1:

Use a Future to do so. Like this:

Meteor.methods({
  my_function: function(arg1, arg2) {

    // Set up a future
    var fut = new Future();

    // This should work for any async method
    setTimeout(function() {

      // Return the results
      fut.ret(message + " (delayed for 3 seconds)");

    }, 3 * 1000);

    // Wait for async to finish before returning
    // the result
    return fut.wait();
  }
});

Update:

To use Future starting from Meteor 0.5.1, you have to run the following code in your Meteor.startup method:

Meteor.startup(function () {
  var require = __meteor_bootstrap__.require
  Future = require('fibers/future');

  // use Future here
});

  Update 2:

To use Future starting from Meteor 0.6, you have to run the following code in your Meteor.startup method:

Meteor.startup(function () {
  Future = Npm.require('fibers/future');

  // use Future here
});

and then use the return method instead of the ret method:

Meteor.methods({
  my_function: function(arg1, arg2) {

    // Set up a future
    var fut = new Future();

    // This should work for any async method
    setTimeout(function() {

      // Return the results
      fut['return'](message + " (delayed for 3 seconds)");

    }, 3 * 1000);

    // Wait for async to finish before returning
    // the result
    return fut.wait();
  }
});

See this gist.

Solution 2:

Recent versions of Meteor have provided the undocumented Meteor._wrapAsync function which turns a function with a standard (err, res) callback into a synchronous function, meaning that the current Fiber yields until the callback returns, and then uses Meteor.bindEnvironment to ensure that you retain the current Meteor environment variables (such as Meteor.userId()).

A simple use would be as the following:

asyncFunc = function(arg1, arg2, callback) {
  // callback has the form function (err, res) {}

};

Meteor.methods({
  "callFunc": function() {
     syncFunc = Meteor._wrapAsync(asyncFunc);

     res = syncFunc("foo", "bar"); // Errors will be thrown     
  }
});

You may also need to use function#bind to make sure that asyncFunc is called with the right context before wrapping it.

For more information see: https://www.eventedmind.com/tracks/feed-archive/meteor-meteor-wrapasync

Solution 3:

Andrew Mao is right. Meteor now has Meteor.wrapAsync() for this kind of situation.

Here's the simplest way to do a charge via stripe and also pass a callback function:

var stripe = StripeAPI("key");    
Meteor.methods({

    yourMethod: function(callArg) {

        var charge = Meteor.wrapAsync(stripe.charges.create, stripe.charges);
        charge({
            amount: amount,
            currency: "usd",
            //I passed the stripe token in callArg
            card: callArg.stripeToken,
        }, function(err, charge) {
            if (err && err.type === 'StripeCardError') {
              // The card has been declined
              throw new Meteor.Error("stripe-charge-error", err.message);
            }

            //Insert your 'on success' code here

        });
    }
});

I found this post really helpful: Meteor: Proper use of Meteor.wrapAsync on server

Solution 4:

Another option is this package which achieves the similar goals.

meteor add meteorhacks:async

From the package README:

Async.wrap(function)

Wrap an asynchronous function and allow it to be run inside Meteor without callbacks.

//declare a simple async function
function delayedMessge(delay, message, callback) {
  setTimeout(function() {
    callback(null, message);
  }, delay);
}

//wrapping
var wrappedDelayedMessage = Async.wrap(delayedMessge);

//usage
Meteor.methods({
  'delayedEcho': function(message) {
    var response = wrappedDelayedMessage(500, message);
    return response;
  }
});