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
- 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
}
});
- 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);
}
}
});
- 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
How Does a Function in a Loop (Which Returns Another Function) Work
Can Jquery .Keypress() Detect More Than One Key at the Same Time
Sorting a JSON Object in JavaScript
How to Get the Nth Occurrence in a String
Getting Title from Wkwebview Using Evaluatejavascript
How to Pause Setinterval() Functions
Explanation of [].Slice.Call in JavaScript
JavaScript Parse Float Is Ignoring the Decimals After My Comma
Detect If Hovering Over Element with Jquery
Cannot Set Boolean Values in Localstorage
Get Column from a Two Dimensional Array
Push Is Overwriting Previous Data in Array
How to Split Comma Separated String Using JavaScript
Is There a Jquery Autogrow Plugin for Text Fields
Template Language That Works on Both Server and Client
React - Changing an Uncontrolled Input
Crbug/1173575, Non-Js Module Files Deprecated. Chromewebdata/(Index)꞉5305:9:5551