How to get the client method.call to wait for an asynchronous function to finish? Currently it reaches the end of the function and returns undefined.
Client.js
Meteor.call( 'openSession', sid, function( err, res ) {
// Return undefined undefined
console.log( err, res );
});
Server.js
Meteor.methods({
openSession: function( session_id ) {
util.post('OpenSession', {session: session_id, reset: false }, function( err, res ){
// return value here with callback?
session_key = res;
});
}
});
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
I was able to find the answer in this gist. In order to run asynchronous code from within a method.call you use Futures which forces your function to wait.
var fut = new Future();
asyncfunc( data, function( err, res ){
fut.ret( res );
});
return fut.wait();
Update: Sorry, I should have read the question more carefully. It looks like this question was also asked and answered here.
Apart from futures, another pattern to consider might be updating another model with the data returned from the asynchronous call and then subscribing to that model's changes.
From the meteor.call documentation it looks like the the result argument (err, res) of your callback function should contain the output of your openSession function. But you aren't returning any values from your openSession function so the return value is undefined.
You can test this:
Client:
Meteor.call('foo', function(err, res) {
console.log(res); // undefined
});
Meteor.call('bar', function(err, res) {
console.log(res); // 'bar'
});
Server:
Meteor.methods({
foo: function() {
var foo = 'foo';
},
bar: function() {
var bar = 'bar';
return bar;
}
});
Related
My friend and I have been struggling with Node.js callbacks since yesterday. We have the following function:
// helperFunction.js
function foo(param) {
request.get({
url: <url>
headers: {<headers>}
}, (err, response, data) => {
array = []
obj.forEach(function (entry) {
// do stuff with array
};
});
return array;
});
}
module.exports.foobar = foo;
then we call that from our app.js.
Since yesterday, we have updated the code to wait for the callback by using a function, like so:
// app.js
//var bar = require('./helperFunction');
//console.log(helperFunction.foobar('param')); // prints undefined
function bar(){
console.log('Log something')
}
foo(bar);
but we don't know how to pass the parameter to foo. I tried to add param (which is a string) to bar but it doesn't work.
For the record, I'm aware of other posts such as this, but I cannot make it work on my code.
In foo you just add a callback parameter and instead of returning you call this function. As a convention, the first parameter of the callback function is the error object. If no error occurred, this object is null and the following parameters are the actual result of the function. Your code didn't include error handling so I added it. If error exists you won't receive any data and foo can't calculate whatever it tries to calculate. In this case, foo should either try to solve the problem itself or propagate the error to its caller.
function foo(param, cb) {
request.get({
url: <url>
headers: {<headers>}
}, (err, response, data) => {
if (err) {
return cb(err);
}
array = []
obj.forEach(function (entry) {
// do stuff with array
};
});
cb(null, array);
});
}
function bar(err, data){
console.log('Log something')
}
foo('some param', bar);
Pass a function to foo. Something like:
foo(() => bar("Hi, I'm something"));
function foo(fn, err) {
if (!err && fn instanceof Function) {
fn();
}
}
function bar(someThing){
console.log(`Log ${someThing}`);
}
I have went through some basic work on callback function and promise. But when I see the following code, I really can't find where did that zzz term come from.
I thought (zzz) is gonna be a input to callback function. inside the callback function, zzz was never defined.
return Dishes.find({}).exec() is only execution which returns nothing. even it returns something, it will go to output of the callback function instead of input, which is (zzz).
sorry if the question is stupid, i just looked at this point for 2 hours and can't figure out... thanks
const mongoose = require('mongoose');
mongoose.Promise = require('bluebird');
const Dishes = require('./models/dishes');
const url = 'mongodb://localhost:27017/conFusion';
const connect = mongoose.connect(url, {
useMongoClient: true
});
connect.then((db) => {
console.log('Connected correctly to server');
var newDish = Dishes({
name: 'Uthappizza',
description: 'test'
});
newDish.save()
.then((zzz) => {
// ??? where did this zzz come from ??????????????????????????????????????????????
console.log(zzz);
return Dishes.find({}).exec();
})
.then((xxx) => {
console.log(xxx);
return db.collection('dishes').drop();
})
.then(() => {
return db.close();
})
.catch((err) => {
console.log(err);
});
});
Since the save() method is asynchronous it returns a Promise, and chaining it to the then() method you entered the callback function and passed it the ‘zzz’ argument.
‘zzz’ is the newDish object that you saved.
You can use the find({}).exec() in any case to find all objects in the database and execute a callback with the results.
When using a function client.call from an npm module (node-celery), it appears that the callback function of client.call is not executed.
Meteor.methods({
'estimates.request'(data) {
var celery = require('node-celery')
var client = celery.createClient({...})
client.on('connect', function() {
console.log('connected'); // this is executed
client.call('proj.tasks.register', [name],
function(err, result) {
console.log('result: ', result); // this is not executed
client.end();
return result;
}
);
});
}
});
Tried wrapping client.call in Meteor.wrapAsync:
callFunc = client.on('connect', function() {
console.log('connected'); // this is executed
client.call('proj.tasks.register', [name],
function(err, result) {
console.log('result: ', result); // this is not executed
client.end();
return result;
}
);
});
callFuncSync = Meteor.wrapAsync(callFunc)
callFuncSync()
but this throws an error in the Meteor server console:
err: [Error: Meteor code must always run within a Fiber. Try wrapping callbacks that you pass to non-Meteor libraries with Meteor.bindEnvironment.]
err: { [Error: read ECONNRESET] code: 'ECONNRESET', errno: 'ECONNRESET', syscall: 'read' }
Question: How should we use Meteor.bindEnvironment to fix this issue?
From the docs,
Wrap a function that takes a callback function as its final parameter. The signature of the callback of the wrapped function should be function(error, result){}
Your code simply wraps the return value of the event attachment call.
You could wrap the whole thing (connection + task calls), but in your case, I would suggest a different approach:
Right now, you are connecting to Celery every time someone is calling the method. I would advise having a persistent connection to Celery, if possible.
The functions that you are trying to wrap do not adhere to the wrapAsync requirements, so you would have to wrap them in a function that does.
In the following code, both the connection and call functions are taken care of. Note that those functions take a cb argument, which will be provided to them by Meteor, and call it with an error and/or result, as appropriate.
those functions are then passed to wrapAsync.
The sync version of them throws if an error is passed to the callback and simulate a synchronous run using a fiber if they are called in a synchronous manner (i.e, no callback is passed). That's the reason for the try..catch block.
import { Meteor } from 'meteor/meteor';
const celery = require('node-celery'); // or `import` it
function createAndConnectAsync(details, cb) {
const client = celery.createClient(details);
const errHandler = function(e) {
cb(e, client);
};
client.once('connect', function() {
client.off('error', errHandler);
cb(null, client); // success
});
client.once('error', errHandler);
}
function callAsync(client, task, args, cb) {
client.call(task, args, function(result) {
cb(null, result);
});
}
const createAndConnect = Meteor.wrapAsync(createAndConnectAsync);
const call = Meteor.wrapAsync(callAsync);
let client;
try {
client = createAndConnect({...}); // returns after the client is connected
} catch(e) {
// connection error
}
Meteor.methods({
'estimates.request'(data) {
// generate `name`
const result = call(client, 'proj.tasks.register', [name]);
return result;
}
});
Combinding async libraries can be tricky, usually they have some helper methods like Meteor's bindEnvironment.
The function returned from Meteor.bindEnvironment also automatically gets run in a Fiber.
Meteor.methods({
'estimates.request'(data) {
var celery = require('node-celery')
var client = celery.createClient({...})
var otherCallback = Meteor.bindEnvironment(function(err, result) {
console.log('result: ', result); // this is not executed
client.end();
return result;
});
var connectCallback = Meteor.bindEnvironment(function() {
console.log('connected'); // this is executed
client.call('proj.tasks.register', [name], otherCallback);
});
client.on('connect', connectCallback);
}
});
FYI: didn't test this because your example isn't 100% complete :)
I have this function in my code :
let request = require("request");
let getDrillDownData = function (userId, query, callback) {
query.id = userId;
let urlQuery = buildUrlFromQuery(query);
request.get({
url: urlQuery,
json: true
}, function (error, response, data) {
if (!error && response.statusCode === 200) {
return callback(null, calculateExtraData(data));
} else if (error) {
return callback(error, null);
}
});
};
and I wish to write some unit test which verify that when the function is called with correct parameters, it is running OK,
and if there is an error, it did return the error
I wrote this unit test code :
describe.only('Server Service Unit Test', function(){
var sinon = require('sinon'),
rewire = require('rewire');
var reportService;
var reportData = require('./reportData.json');
beforeEach(function(){
reportService = rewire('../../services/reports.server.service');
});
describe('report methods', function(){
var reportData;
var query = { id: "test"};
var userId = 'testuser';
var getDrillDownData;
var request;
beforeEach(function(){
getDrillDownData = reportService.__get__('getDrillDownData');
});
it ('should get drill down data by userId and query', function(done){
var getCallback = sinon.stub();
request = {
get: sinon.stub().withArgs({
url: query,
json: true
}, getCallback.withArgs("error", {statusCode: 200}, reportData))
};
reportService.__set__('request', request);
getDrillDownData(userId, query, function(err, returnData){
(err === null).should.eql(true);
//(getCallback.withArgs(undefined, {statusCode: 200}, reportData).calledOnce).equal(true);
done();
});
});
});
But I keep getting this error:
Error: timeout of 2000ms exceeded. Ensure the done() callback is being called in this test.
Can someone help?
Thanks
I would stub request.get() directly:
describe('report methods', function() {
// Make `request.get()` a Sinon stub.
beforeEach(function() {
sinon.stub(request, 'get');
});
// Restore the original function.
afterEach(function() {
request.get.restore();
});
it ('should get drill down data by userId and query', function(done) {
// See text.
request.get.yields(null, { statusCode : 200 }, { foo : 'bar' });
// Call your function.
getDrillDownData('userId', {}, function(err, data) {
...your test cases here...
done();
});
});
});
Using request.get.yields() (which calls the first function argument that Sinon can find in the argument list; in this case, it's the (error, response, data) callback that gets passed to request.get() in your function) you can tell Sinon which arguments to use to call the callback.
That way, you can check if the callback to request.get() handles all arguments properly.
You can use .withArgs() too (request.get.withArgs(...).yields(...)), although you have to be sure that you're using it correctly; otherwise, if the exact arguments don't match, Sinon will call the original request.get() instead of using the stubbed version.
Instead, I prefer using stub.calledWith() to check for the correct arguments after the call has been made. That integrates much better with Mocha as well, as you can use explicit assertions.
I am trying to get data from a function in a node module, that returns a json object, and show the json object in a router in my server.js file.
I am trying to export it like this:
// Export the function
exports.getTweets = function() {
var tweetResult = {};
twitter.get(
'/statuses/user_timeline.json',
{ screen_name: 'twitter', count: 5},
function (error, tweets) {
tweetResult = tweets;
}
);
return tweetResult;
};
And here is my server.js file, with the route that should return the json object
tweets = require("./twitter_stream.js"),
// set up the route for the json object
app.get("/tweets.json", function (req, res) {
console.log(tweets.getTweets());
});
I haven't been able to get anything but an empty object.
You need to pass a callback to the getTwitter function to receive the twitter result. The variable tweetResult is null when the code return tweetResult; is invoked.
Please check the code below to see how to use the callback in your code:
exports.getTweets = function(callback) {
twitter.get(
'/statuses/user_timeline.json',
{ screen_name: 'casperpschultz', count: 5},
function (error, tweets) {
//invoke the callback function passing the tweets
callback(tweets);
}
);
};
Code to invoke the getTweets function passing a callback function:
app.get("/tweets.json", function (req, res) {
//invoke getTweets passing a callback function by parameter
tweets.getTweets(function(tweets){
console.log(tweets);
});
});
Node.js works in an asynchronous way with callback, here is a good reference that explains it.