I've got a very interesting issue in my Meteor js app: when I call a meteor method inside of my template's onCreated method the callback for that method call sometimes returns immediately with undefined as the result. It turns out that this is because the template got created as a result of running a meteor method simulation.
Two questions:
Is is this a bug? It certainly isn't the behavior that I expected.
How can I work around this without using weird hacks like setTimeout (and, by the way Meteor.setTimeout isn't allowed inside of method simulations)?
Some Code:
// My Template (Not my real code, just to demonstrate)
Template.saying.onCreated(() => {
var tmpl = Template.instance();
tmpl.saying = new ReactiveVar();
Meteor.call('getSaying', (err, saying) => {
// If called inside of a simulation, saying is null
tmpl.saying.set(saying);
});
});
// Assume that the above template is used in an {{each}} block
// and somewhere in my code I call this
Items.insert({});
// Because Items.insert wraps a meteor method which also runs as a
// simulation on the client, then the Template.saying.onCreated
// callback will be called in the context of an active simulation,
// which means that 'getSaying' method call will return immediately
// with undefined as the result.
Two possibilities to avoid simulation:
Define the method in a server side only file
Use isClient. When the simulation is run it will evaluate to true
if (Meteor.isClient) {
// on the client, the return value of a stub is ignored
return;
}
Related
I'm working on a Meteor project and want to get the return value of Meteor.call in template helpers on client side. At very first, I just set a variable in the call back function and get the variable's value outside the Meteor.call. I found out the code after Meteor.call doesn't execute at all. Then I searched a bit and use Session, it works. But I don't really understand the reason. Here's my original code and modified code. Can anyone explain a bit for me? Thanks!!
Original wrong code: html
<div id="text-result-main">
<h2>{{title}}</h2>
</div>
js
Template.texts.helpers({
title: function(){
var index = Router.current().params.index;
Meteor.call('getTitle', index,function(error, result){
titles = result;
});
console.log(titles);
return titles;
}});
Collection text.js
Text = new Mongo.Collection("text");
Meteor.methods({
'getTitle': function(myindex){
return Text.findOne({index: myindex}).title;
}});
The working code: js
Template.texts.helpers({
title: function(){
var index = Router.current().params.index;
Meteor.call('getTitle', index,function(error, result){
Session.set("titles",result);
});
console.log(Session.get("titles"));
return Session.get("titles");
}});
Notice that I didn't publish Collection Text to the client at all because it's just so huge. Every time when I refresh the page when running the wrong code, I can't see the content of "title" or see it on the console. But when I set the session, it works. I don't really understand how it works here. Thanks
There is two issues Asynchronicity and Reactivity
This affectation
Meteor.call('getTitle', index,function(error, result){
titles = result;
});
inside the meteor call is executed but in a asynch way. So the return of your helper is immediately called, and return a empty value.
Try it out in the console of your browser.
But then, why your template render correctly with {{title}} when you use a Session Variable ?
It's because the Session is a reactive data source, witch means that every change to it trigger a re-computation of all templates involving this piece of data.
Here is a timeline:
Methods is called
Return empty value
Method is executed, setting variable value
If the Variable is a reactive data source, template is re-computed. ( in your case, the session is a reactive data source. )
To go further
I would use a reactive var in that case, it's very close from a session variable, but the scope is limited to a template.
A good read on Reactive data source: http://richsilv.github.io/meteor/meteor-reactive-data-types/
The problem is the fact that Meteor.call() is asynchronous when paired with a callback.
So when title() starts executing, it does not wait for your Meteor.call() invocation to return a result (or possibly an error). It continues execution. This is called asynchronous execution.
In short, you are trying to log the value for the key titles which doesn't exist in Session (since the state of your asynchronous Meteor call is unknown, at this point of time).
Try moving the console log statement into the callback paired with your Meteor.call() and you can see the result once it has successfully been set in Session.
A workaround to your problem is to make your Meteor.call() synchronous like this:
Template.texts.helpers({
title: function(){
var index = Router.current().params.index;
var result = Meteor.call('getTitle', index); // <--- this is synchronous code now
Session.set("titles",result);
console.log(Session.get("titles"));
return Session.get("titles");
}});
Removing the callback makes Meteor.call() behave synchronously.
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.
(from http://docs.meteor.com/api/methods.html#Meteor-call)
Why not use something like this:
title: function(){
var index = Router.current().params.index;
var a = Text.findOne({index: myindex}).title;
console.log(a);
return a;
without methods
Consider this (simplified) extract from a class:
function PluginsEngine() {
this.allPluggins = require('./plugins');
};
PluginsEngine.prototype.applyPlugins = function(incomingData, done){
// bind the incoming data to each plugin
var key;
for (key in this.allPluggins)
this.allPluggins[key] = this.allPluggins[key].bind(undefined, incomingData);
async.parallel(this.allPluggins, done);
};
The constructor loads a set of anonymous functions and the "applyPlugins" method then passes the "incomingData" (string) to each and these are executed using async.
When writing tests against this I am seeing a strange behavior in that calling "applyPlugins" runs fine, but calling it again immediately afterwards causes the error:
TypeError: string is not a function
Adding some additional debug, I can see that allPluggins is empty (functions not available) when run the second time.
Can anyone advise why the variable is losing the functions?
With
var twitPost = Meteor._wrapAsync(twit.post.bind(twit));
function process(screen_name)
{
twitGet('users/show', {'screen_name': screen_name});
}
a synchronous call to process("screen_name") works fine, but
stream.on('tweet', function(tweet)
{
process(tweet.user.screen_name);
});
yields Error: Meteor code must always run within a Fiber. Try wrapping callbacks that you pass to non-Meteor libraries with Meteor.bindEnvironment.
Any ideas how/if I could make this work? I would like to go via some processing function that can do other stuff except call twitPost.
As it is written, methods that use Meteor code (particularly ones that access Collections) need to be wrapped with a Fiber. One way to do so is to use Meteor.bindEnvironment:
stream.on('tweet', Meteor.bindEnvironment(function(tweet) {
process(tweet.user.screen_name);
}));
I have a simple function which routes a HTTP query pattern, queries redis and sends a response. The following is the code
router.get('/getinfo/:teamname', function main(teamname) {
rclient.hgetall(teamname,function(err,obj){
console.log("the response from redis is ",obj)
cache.put(eventname,obj);
console.log("inserting to cache");
this.res.end(obj); // this object is root cause for all problems
});
}
The router object afaik, sends the response using this.res.end(obj) . I guess since I am trying to do this inside my redis client , I am getting error. Is there any other way to send the value as a response ? I thought of using emitter based model where the channel emits the response and listener gets it. but it feels like a round about way to solving this problem. Is there any simpler approach ?
The error may be because, where you're trying to use this, it doesn't have the intended value -- an object with a res property that in turn has an end() method.
That would be because every function in JavaScript has its own this with its own value. And, when nesting functions, using this will return the value for the closest function (i.e. shadowing).
To resolve that, you can save the intended value to a local variable:
router.get('/getinfo/:teamname', function main(teamname) {
var request = this;
rclient.hgetall(teamname,function(err,obj){
// ...
request.res.end(obj);
});
});
Or, bind the anonymous callback so both functions are forced to have the same this value:
router.get('/getinfo/:teamname', function main(teamname) {
rclient.hgetall(teamname, function(err,obj){
// ...
this.res.end(obj);
}.bind(this));
});
I have a problem with QlikView in the browser: I have a listbox and try to access it using an initialize script.
The script is registered by using the InitWorkbench function, using its BodyOnLoadFunctionNames parameter. So far, this works, and the initializer is run at startup.
Inside the initializer I try to do the following:
var doc = Qv.GetCurrentDocument();
var listbox = doc.GetObject('LB01');
Afterwards, when I have a look at listbox.Type, unfortunately it is undefined. If I delay execution of this query, it correctly says LB, hence apparently the query works - but only when it is executed delayed.
So, obviuosly there's a timing problem, and it seems as if the initializer runs too early (or I am doing something wrong).
Can anybody point out what the solution is (or give me a hint on what I am doing wrong)?
Okay, I've found the solution: The internal update function did not run yet, and all the values are only available once this function ran, so you need to provide a callback to the call to GetObject (that gets called after the update function):
var doc = Qv.GetCurrentDocument();
var listbox = doc.GetObject('LB01', function () {
console.log(listbox.Type); // => 'LB'
});