Meteor: Calling an Asynchronous Function Inside a Meteor.Method and Returning the Result

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

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

How can i get result of a meteor async method in my client

you could try with Promises, like so :

async function myRequest(options) {
return new Promise(resolve => {
PythonShell.run('converter.py', options, function (err, result) {
if (err) throw err;
console.log("in my request")
console.log(result);
resolve(result);
});
});
}

Can't test now, but it should work.

How to return a result from Meteor.call method in client?

Because the return statement is in the callback and is unblock you can't return a method value like that. you can put it https://github.com/stubailo/meteor-reactive-method which will return the value but it must live inside a helper or reactive environment.

How to wait for meteor call response and then execute other statements in javascript?

I have made some research on the various options for such a situation as some others here might have faced it already, too.

Option A- Nested calls in client

The first and most obvious one is to do nested calls. This means to call the next function after the result has been received in the callback.

// level 1
Meteor.call('methodCall', param1, param2, param3, function (error, result) {
// level 2
if (error) console.log(error.reason);

Session.set("xyz",result);

Meteor.call('methodCall',result, param2, param3, function (error, result) {
// level 3...
if (error) console.log(error.reason);

console.log("result: "+result);
Session.set("cdf",result);
});

});

Pros: classic js way, no fancy new concepts required, server methods sticks so a simple logic while client dies the complex work

Cons: ugly, can cause confusion and sometimes hard to debug

Requires: Template.autorun or Tracker.autorun to capture the changes from Session reactively.


Option B - Wrap Async

Many might have already found this method to be no.1 choice for structuring async code into sync code.

Fibers (and wrapAsync utilizing fibers) make the code only look to be sync but the nature of execution remains async. This works the same way like Promises work or like async/await works.

Pros: powerful when in a single environment

Cons: not to be used with Meteor.call

Requires: a fiber to run in

Problem with Meteor.call

However, you can't easily call a Meteor method using this feature. Consider the following code

const param1 = "param1";
const param2 = "param2";
const param3 = "param3";

const asyncCall = Meteor.wrapAsync(Meteor.call);
const result1 = asyncCall("methodCall", param1, param2, param3);
// result1 will be undefined

To further explain I will cite the documentation:

On the client, if you do not pass a callback and you are not inside a
stub, call will return undefined, and you will have no way to get the
return value of the method. That is because the client doesn’t have
fibers, so there is not actually any way it can block on the remote
execution of a method.

Summary:Meteor.wrapAsync is not to be utilized together with Meteor.call.


Option C - Bundle in one method

Instead of trying to create a synced sequence of meteor calls, you could also provide all parameters and logic to a single server method, that returns an object which keeps all returned values:

client.js

const param1 = "param1";
const param2 = "param2";
const param3 = "param3";

Meteor.call('methodCall', param1, param2, param3, function (err, result) {
const xyz = result.xyz;
const cdf = result.cdf;
});

server.js

function _methodCall(p1, p2, p3) {
// ...
return result;
}

Meteor.methods({
'methodCall'(p1, p2, p3) {
const result1 = _methodCall(p1, p2, p3);
const result2 = _methodCall(result1, p2, p3);
return {
xyz: result1,
cdf: result2,
}
}
})

This will create a sequential execution (by following the sequential logic you provided in your question) and returns all it's results in a bundled object.

Pros: sequential as desired, one request - all results
Cons: one extra method to be tested, can introduce tight coupeling between methods, return objects can become large and complex to parse for the clinet
Requires: a good sense for method design

If I find other options I will add them to this post.

Meteor.call in client doesn't wait when the called method has another call inside

Sync call on the server

Using Meteor.call on the server does not require a callback, unless you really want to work async on the server side.

If you do not pass a callback on the server, the method invocation
will block until the method is complete. It will eventually return the
return value of the method, or it will throw an exception if the
method threw an exception. (Possibly mapped to 500 Server Error if the
exception happened remotely and it was not a Meteor.Error exception.)

Instead of passing a callback you would either return the result

return Meteor.call(...)

or assign it to a variable that is used for further processing.

const retVal = Meteor.call(...)


Better way: Externalize shared code

If two meteor methods rely on the same code (e.g. one is calling the other) you should extract this code into a shared function. This makes testing and tracing errors also easier.

server/api/common.js

export const sharedFunction = function(param1) {
// ... do somethin
return OtherStuff.findOne(query);
}

server/api/stuff.js:

import { sharedFunction } from './common.js';

Meteor.methods({
'stuff.someMethod'(param1){
// ...
const temp = sharedFunction(param1);
// ...
return result; // or temp if this should be returned to client
}
})

server/api/otherstuff.js

import { sharedFunction } from './common.js';

Meteor.methods({
'otherstuff.someOtherMethod'(param1){
return sharedFunction(param1);
}
});

Using the sharedFunction follows the concepts of DRY and Single Point of Failure.

How can I execute a callback on a meteor method that does async calls?

What you are doing now is passing a function to the server. If that does work, it's very insecure. What you want to do is create a future and then use it to manage the asynchronous function. Here is an example:

let Future = Npm.require('fibers/future');
Meteor.methods({
someRequest: function (someArg) {
// for security reasons, make sure you check the type of arguments.
check(someArg, String);

// future is an async handler.
let future = new Future();
// this is a function for a generic node style callback [ie, function (err, res)]
let resolver = future.resolver();

// run your function and pass it the future resolver
// the future will be resolved when the callback happens.
anAsyncFunction(someArg, resolver);

// this meteor method will not end until future has been resolved.
return future.wait();
}
});

Alternatively, Meteor provides a wrapAsync that provides similar functionality of wrapping async functions in futures so they can run in meteor methods. That is:

let wrappedAsyncFunction = Meteor.wrapAsync(anAsyncFunction /** second argument is `this` binding*/);
return wrappedAsyncFunction();

Returning value from multiple promises within Meteor.method

  1. With Meteor methods you can actually simply "return a Promise", and the Server method will internally wait for the Promise to resolve before sending the result to the Client:
Meteor.methods({
getLogos(images) {
return client
.logoDetection(images[0]) // Example with only 1 external async call
.then(results => {
const logos = results[0].logoAnnotations;
if (logos != '') {
return logos.map(logo => logo.description);
}
}); // `then` returns a Promise that resolves with the return value
// of its success callback
}
});

  1. You can also convert the Promise syntax to async / await. By doing so, you no longer explicitly return a Promise (but under the hood it is still the case), but "wait" for the Promise to resolve within your method code.
Meteor.methods({
async getLogos(images) {
const results = await client.logoDetection(images[0]);
const logos = results[0].logoAnnotations;
if (logos != '') {
return logos.map(logo => logo.description);
}
}
});

  1. Once you understand this concept, then you can step into handling several async operations and return the result once all of them have completed. For that, you can simply use Promise.all:
Meteor.methods({
getLogos(images) {
var promises = [];
images.forEach(image => {
// Accumulate the Promises in an array.
promises.push(client.logoDetection(image).then(results => {
const logos = results[0].logoAnnotations;
return (logos != '') ? logos.map(logo => logo.description) : [];
}));
});
return Promise.all(promises)
// If you want to merge all the resulting arrays...
.then(resultPerImage => resultPerImage.reduce((accumulator, imageLogosDescriptions) => {
return accumulator.concat(imageLogosDescriptions);
}, [])); // Initial accumulator value.
}
});

or with async/await:

Meteor.methods({
async getLogos(images) {
var promises = [];
images.forEach(image => {
// DO NOT await here for each individual Promise, or you will chain
// your execution instead of executing them in parallel
promises.push(client.logoDetection(image).then(results => {
const logos = results[0].logoAnnotations;
return (logos != '') ? logos.map(logo => logo.description) : [];
}));
});
// Now we can await for the Promise.all.
const resultPerImage = await Promise.all(promises);
return resultPerImage.reduce((accumulator, imageLogosDescriptions) => {
return accumulator.concat(imageLogosDescriptions);
}, []);
}
});


Related Topics



Leave a reply



Submit