Writing/Converting Meteor Synchronous Functions - asynchronous

This has been bothering me for a while so I thought I'd just do a quick QA on it:
If one has a normal nodeJS module or something and it has a async function on the server side. How do I make it synchronous. E.g how would I convert the nodejs fs.stat asynchronous function to a synchronous one.
e.g I have
server side js
Meteor.methods({
getStat:function() {
fs.stat('/tmp/hello', function (err, result) {
if (err) throw err;
console.log(result)
});
}
});
If I call it from the client I get back undefined as my result because the result is in a callback.

There is a function (undocumented) called Meteor.wrapAsync.
Simply wrap the function up
Meteor.methods({
getStat:function() {
var getStat = Meteor._wrapAsync(fs.stat);
return getStat('/tmp/hello');
}
});
Now you will get the result of this in the result of your Meteor.call. You can convert any async function that has a callback where the first parameter is an error and the second the result.

Related

Meteor method + check() + audit-argument-checks + async/await causes exception

Iā€™m using meteor with check() and audit-check-arguments package.
When I use a meteor method using async/await and pass a parameter, even though I use check() to validate the function parametrs, the audit package still throws an exception indicating that not all input parameters have been checked. If I remove the async/await implementation, the package does not crib. What am I missing?
Example:
Meteor.methods({
test: async function(param1){
check(param1, String);
...
await ....
}
});
Throws an exception:
=> Client modified -- refreshing
I20200513-10:43:27.978(5.5)? Exception while invoking method 'test' Error: Did not check() all arguments during call to 'test'
I20200513-10:43:27.979(5.5)? at ArgumentChecker.throwUnlessAllArgumentsHaveBeenChecked (packages/check/match.js:515:13)
Whereas this traditional meteor method does not throw any exceptions
Meteor.methods({
test: function(param1){
check(param1, String);
...
}
});
I know for sure that I am passing exactly one parameter.
It looks like audit-argument-checks only works for synchronous functions.
I don't have this issue because we use mdg:validated-method, which uses requires you to specify an argument validator for each method.
It shuts up the argument checker by wrapping the method function with this:
// Silence audit-argument-checks since arguments are always checked when using this package
check(args, Match.Any);
The simplest solution I can think of, is to separate the check from the async function. You could use a wrapper function to do this:
function checkAndRun(check, run) {
return function(...args) {
check.apply(this, args);
return run.apply(this, args);
}
}
Meteor.methods({
'example': checkAndRun(
function(exampleID){
check(exampleID, String);
},
async function(exampleID) {
const result = await doSomethingAsync(exampleID);
SomeDB.update({ _id: exampleID }, { $set: { someKey: result.value } });
return result.status;
}
}
});
or you could even do it inline with an async IIFE:
Meteor.methods({
example(exampleID) {
check(exampleID, String);
return (async () => {
const result = await doSomethingAsync(exampleID);
SomeDB.update({ _id: exampleID }, { $set: { someKey: result.value } });
return result.status;
})()
}
});
Which, come to think of it, is much simpler than the simplest solution I could think of at first šŸ™ƒ
You just want to separate the sync check from the async method body somehow
In case you're curious, let diving through the source to see where it's called. When the method is called (in ddp-server/livedata-server), we end up here, a sync method call for the first reference of audit-argument-checks:
https://github.com/meteor/meteor/blob/master/packages/ddp-server/livedata_server.js#L1767-L1770
Which takes us into check/Match for another sync call here: https://github.com/meteor/meteor/blob/71f67d9dbac34aba34ceef733b2b51c2bd44b665/packages/check/match.js#L114-L123
Which uses the strange Meteor.EnvironmentVariable construct, which under the hood has another sync call: https://github.com/meteor/meteor/blob/master/packages/meteor/dynamics_nodejs.js#L57

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

I am following what I've read around being the standard way to use Meteor.call but it's behaving strangely in this scenario:
Client:
Template.sometemplate.events({
'submit .somebutton'(event){
...
Meteor.call('stuff.someMethod', param1, function (err, res){
console.log(err);
console.log(res);
};
}
})
Server /api/stuff.js:
Meteor.methods({
'stuff.someMethod'(param1){
...
Meteor.call('otherstuff.someOtherMethod', param1, function(err, res){
if(err){ throw new Meteor.Error(400,'wrong things');}
if(res) { return 'ok';}
}
);
}
})
Server /api/otherstuff.js:
Meteor.methods({
'otherstuff.someOtherMethod'(param1){
...
return OtherStuff.findOne(query);
}
})
On the client side I click and immediately see the console.log for both err and res as undefined. Whereas in other parts of the application when the client calls a server method, which is not calling another method, the client waits for the answer before executing the asynch callback.
There must be something wrong in how I use the Meteor.call inside a server method calling another server method. The scenario is that for instance I want to insert a document and while doing so I want to check some values in order to link it to other documents from other collections.
Thank you very much,
T.
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.

Error when calling http method from client in meteor

I am trying to consume a REST API in the meteor application. Inside the server.js file which is in the server folder, I have written this code:
Meteor.methods({
checkTwitter: function () {
this.unblock();
return Meteor.http.call("GET", "http://search.twitter.com/search.json?q=perkytweets");
}
});
Inside the client.js file, which is in the client folder, I have written down this code:
Meteor.call("checkTwitter", function(error, results) {
console.log(results.content); //results.data should be a JSON object
});
I get this error message in console:
"Exception while simulating the effect of invoking 'checkTwitter' Error: Can't make a blocking HTTP call from the client; callback required".
I have the callback function defined in client due to which I don't understand this error. What is it that I am doing wrong?
Meteor.http has been deprecated, please see the HTTP package.
I think that since there's a stub, "checkTwitter" will actually also run on the client. Once the server returns, its result will overwrite the result from teh client run.
In this case, since Meteor.http.call can't run on the client without a callback, you get the error.
Try changing:
Meteor.methods({
checkTwitter: function () {
this.unblock();
return Meteor.http.call("GET", "http://search.twitter.com/search.json?q=perkytweets");
}
});
With
Meteor.methods({
checkTwitter: function () {
if (Meteor.isServer) {
this.unblock();
return Meteor.http.call("GET", "http://search.twitter.com/search.json?q=perkytweets");
}
}
});

Meteor callback to sys.exec inside a Meteor.call callback

I have an event triggering a Metor.call():
Meteor.call("runCode", myCode, function(err, response) {
Session.set('code', response);
console.log(response);
});
But my runCode function inside the server's Metheor.methods has inside it a callback too and I can't find a way to make it return something to response in the above code.
runCode: function(myCode) {
var command = 'pwd';
child = exec(command, function(error, stdout, stderr) {
console.log(stdout.toString());
console.log(stderr.toString());
// I Want to return stdout.toString()
// returning here causes undefined because runCode doesn't actually return
});
// I can't really return here because I don't have yet the valuer of stdout.toString();
}
I'd like a way to have the exec callback return something as runCode without setInterval which would work, but as a hacky way in my opinion.
You should use Future from fibers.
See docs here : https://npmjs.org/package/fibers
Essentially, what you want to do is wait until some asynchronous code is run, then return the result of it in a procedural fashion, this is exactly what Future does.
You will find out more here : https://www.eventedmind.com/feed/Ww3rQrHJo8FLgK7FF
Finally, you might want to use the Async utilities provided by this package : https://github.com/arunoda/meteor-npm, it will make your like easier.
// load future from fibers
var Future=Npm.require("fibers/future");
// load exec
var exec=Npm.require("child_process").exec;
Meteor.methods({
runCode:function(myCode){
// this method call won't return immediately, it will wait for the
// asynchronous code to finish, so we call unblock to allow this client
// to queue other method calls (see Meteor docs)
this.unblock();
var future=new Future();
var command=myCode;
exec(command,function(error,stdout,stderr){
if(error){
console.log(error);
throw new Meteor.Error(500,command+" failed");
}
future.return(stdout.toString());
});
return future.wait();
}
});

Client Side Asynchronous Call in Meteor?

I have a Meteor client-side asynchronous call that I can't seem to get a return value from. I know I can't use futures on the client-side, so I'm stuck.
And since the Meteor.call() is from the client-side, it has to be asynchronous. It looks like this:
Meteor.call('DirList', path, function(error, result) {
console.log(result);
});
The console.log() works fine, but how do I get the result back into the surrounding function?
Bob
You can store the result in a Session variable and then do the logic with the variable inside a Deps.autorun context. Like:
Meteor.call('DirList', path, function(error, result) { Session.set('result', result); });
Deps.autorun(function (c) {
var result = Session.get('result');
if (!result) return;
c.stop();
alert(result);
});

Resources