0

Everything I have read about scope in Meteor tells me that using the 'var' keyword in front of a variable declaration will give that variable 'file' scope whereas omitting the 'var' keyword will give it 'package' scope.

I don't think that's the whole story, though. I think that the only time variables have true 'file' scope is when they are declared outside of any brackets. Otherwise, their scope seems to be limited to within those brackets.

Even more confusing is an issue I am facing right now where properties of the same object have a different value depending on whether they are accessed from inside or outside of brackets, in this particular case a call to a Meteor method.

Here is some code to illustrate what I mean. I declare the object outside of any brackets and then access properties of it and assign other values to other properties of it from inside 'submit form', one of the events contained within 'Template.inputNamesForm.events'. First, I populate its 'inName' property with a string value contained in a text box inside my form as follows:

var name1 = {};

Template.inputNamesForm.events({
  'submit form': function (event) {

    event.preventDefault();
    name1.inName = event.target.firstName.value;

  Meteor.call('convertName', name1.inName, function (error, result) {
      name1.outName = result;
      console.log("Value as seen from inside call after button click:         name1.inName = " + name1.inName + " -- name1.outName = " + name1.outName);
    });

    console.log("Value as seen from outside call after button click: name1.inName = " + name1.inName + " -- name1.outName = " + name1.outName);
  }
)};

After assigning a string value to name1.inName, I use that property to invoke a call to a Meteor method. The output of that method is another string which I assign to a different property of the same object, ie. name1.outName.

My expectation is that the properties of this object will contain consistent values anywhere they are accessed from within this file. After all, they are supposed to have 'file' scope, aren't they?

What actually happens, though, is different. The value of name1.outName is different depending on whether I access it from inside the callback of my Meteor method call or outside of it.

If I call it from inside the callback, it has the expected value. If I call it from outside the callback, its value is undefined after the first button click. If the button is clicked again, however, then it will take a new value. It is always exactly one button click away from, ie. behind, where I would expect it to be, ie. from its value when called from within the callback.

Equally surprising is that the console.log command located after the Meteor method call gets executed before the console.log command which appears above it inside the callback.

I have to conclude that, if this parameter can have two values at any given time, then there must be two name1 objects, one existing inside the Meteor methods's callback brackets and another outside them. But where was this second version invoked/declared?

Another observation: Even declaring a variable as global by omitting the 'var' keyword doesn't seem to overcome the problem with items within brackets not being seen by items outside. I just really believe that scope in Meteor is a lot more complicated than anything I have read suggests. And I really wish there were some sort of comprehensive guide available for explaining just exactly how it works.

In any case, here is what I hope to get in the way of answers to this query:

I want to know how I can make the value of variables be consistent throughout the code. More importantly, I want values to be correct after just one button click, ie. I don't want users to have to click the button twice to get the right information.

I'd appreciate any help anyone can offer.

Meanwhile, I have checked these sources (among others):
https://medium.com/meteor-js/meteor-managing-the-global-namespace-5a50080a05ea#.t8gz1h3oa
http://blog.b123400.net/how-does-meteor-manage-scope/

I've also looked at these threads and several others, most of which have just confused me more:
Meteor variable scope (global, client, server, or all?)
Global variables in Meteor

Community
  • 1
  • 1
  • It seems the strategy for using two spaces to create a newline does not work within a comment. Is that correct? – Darwood Horace Mar 03 '16 at 23:53
  • 1
    It's not about scope, but about execution order. The callback is fired *after* the second `console.log()` call, as it is an async callback and it is executed after the Meteor method call is done. – MasterAM Mar 04 '16 at 00:14
  • Possible duplicate of [How do I return the response from an asynchronous call?](http://stackoverflow.com/questions/14220321/how-do-i-return-the-response-from-an-asynchronous-call) – Kyll Mar 25 '16 at 09:04

5 Answers5

1

I'm not sure of the entire scope of your question, so I'm just focusing on your code:

var name1 = {};

Template.inputNamesForm.events({
  'submit form': function (event) {

    event.preventDefault();
    name1.inName = event.target.firstName.value;

    Meteor.call('convertName', name1.inName, function (error, result) {
      name1.outName = result;
      console.log("Value as seen from inside call after button click:         name1.inName = " + name1.inName + " -- name1.outName = " + name1.outName);
    });

    console.log("Value as seen from outside call after button click: name1.inName = " + name1.inName + " -- name1.outName = " + name1.outName);
  }
)};

I can tell you that the reason you're seeing name1.outName as undefined when your first console log happens is because the convertName method call happens asynchronously. You could literally set name1.outName to anything before the method call happens and you're going to see that value in the first console.log output:

var name1 = {};

Template.inputNamesForm.events({
  'submit form': function (event) {

    event.preventDefault();
    name1.inName = event.target.firstName.value;
    name1.outName = "Look at me!"; // Setting name1.outName
    Meteor.call('convertName', name1.inName, function (error, result) {
      name1.outName = result;
      console.log("Value as seen from inside call after button click:         name1.inName = " + name1.inName + " -- name1.outName = " + name1.outName);
    });

    // This is going to log "Look at me!" for name1.outname the first time.
    console.log("Value as seen from outside call after button click: name1.inName = " + name1.inName + " -- name1.outName = " + name1.outName);
  }
)};
Stephen Woods
  • 4,049
  • 1
  • 16
  • 27
  • Thanks very much for your input, Stephen and @MasterAM. I understand the issue with asyncronous execution, but I thought using a callback in which to extract the output value of the function called was supposed to solve that problem. It looks to me as if my console.log statement *does* appear *after* the callback, ie. after the closing bracket + parenth + semi-colon with which the callback ends. If I put my console.log any further down in the code, it will appear outside the event in which I want it to run, won't it? Any thoughts on how I can solve this? I'm lost. – Darwood Horace Mar 05 '16 at 07:09
  • Meanwhile, it appears that my notification did not take. Nor did my line breaks. I don't understand. I've read the instructions for markup language in comments twice, and they don't seem to work: http://stackoverflow.com/editing-help#comment-formatting I can't figure out what I'm doing wrong. – Darwood Horace Mar 05 '16 at 07:15
  • Maybe the answer is to just do everything inside the callback? I thought that must be it, but that won't work because I need to run the call twice on two different pieces of data and then compare them. So maybe there's a 'wait' command of some kind that can hold things up until the callback runs? – Darwood Horace Mar 05 '16 at 07:20
  • 1
    That really depends on your use case. You may use scoped variables to store both names, or fire the other method call from within the first callback (resulting in what is known as "callback hell"), or use a pattern like "promise" to make it more readable, or refactor your code so that it does not require 2 calls. Anyhow, this is a separate question and not really related to this one (as SO questions need to be specific). – MasterAM Mar 05 '16 at 12:22
0

It appears that I need to use wrapAsync in some form or another to resolve this problem. I suspect the answer lies in this thread, although I have not yet been able to completely decipher the code examples here:

https://forums.meteor.com/t/how-do-fibers-and-meteor-asyncwrap-work/6087/35

Frankly, I'm amazed at how difficult Meteor is to use. Virtually every time I try to do something new in this language it seems like I'm the first one to have ever done it. I'm reminded of living in Kuwait where, anytime I had to deal with that Third World bureaucracy, it took multiple visits to various offices to get issues resolved. Things that anyone living in that country had to get done on a routine basis. Yet no one in the offices ever seemed to know how to do them.

I'm sorry to gripe, but I've been at this for months (starting in May 2015) and have yet to get even the simplest app off the ground. Seriously, how can there not be a simple and straightforward way to call a function, retrieve information from it, and then use that information in a follow-up command? Is that such an unusual thing to need to do?

  • If you think Meteor is hard to use I suspect you haven't tried to implement something similar with JavaScript before. It does really solve the hard problems you usually encounter. In your case, you write code that runs on the client, right? Then you can't use fiber/wrap async, that only works on the server. "how can there not be a simple and straightforward way to call a function, retrieve information from it, and then use that information in a follow-up command?" That's what we use callbacks for. The last argument passed to `Meteor.call` will be called when you've got your result. – Peppe L-G Mar 25 '16 at 06:13
  • Thanks for your response, Peppe. My earlier understanding was that using a callback would solve my problem. However, I am now learning that callbacks only solve part of the problem. You also need asyncWrap, which is also not foolproof. And, to complicate things, there are multiple ways to implement asyncWrap as can be seen if you look at the link I posted in my answer. All this to call a function. Meanwhile, by the time I get around to working on the actual functionality of my app, I'm so exhausted from trying to figure out Meteor that I don't have the energy anymore. – Darwood Horace Mar 25 '16 at 21:11
  • Another thing I don't understand is why Meteor requires so many add-ons that don't work in regular node.js. I like to test out my code at the command line, but this is virtually impossible when half of it is made up of Meteor-specific functions which aren't understood by node.js. – Darwood Horace Mar 25 '16 at 21:14
  • I wrote a longer answer which might answer some of your questions. Regarding your second comment, if code in a Meteor package can't be used in a Node JS environment, that Meteor package is probably dependent on some other package (possibly Meteor itself). If not, the package author might have a dependency on Meteor/a Meteor package in vain. Why Meteor created their own package system was probably because the package system for Node JS was only designed for Node JS (where all your JavaScript code runs on the server), while Meteor has both server and client side code. – Peppe L-G Mar 26 '16 at 19:50
0

Seems likes you are overthinking it. To explain it in simple terms, the callback function that you passed to Meteor.call will run asynchronously, meaning the it will not block other operations until it is finished. Think of the following example in pure javascript.

var sampleObj = {};

setTimeout(function () {
    sampleObj.testField = "I am test field.";
    console.log("Inside timeout: " + sampleObj.testField);
}, 100);

console.log("outside of timeout: " + sampleObj.testField);

See the JSFiddle.

In this case also the second console.log prints before the first one and the value is undefined. Here the reason is setTimeout will execute this result after 100 milliseconds and doesn't block execution of other code. Same is the case with your Meteor.call, instead of executing the code after 100ms, it waits until it gets result from server.

Kishor
  • 2,659
  • 4
  • 16
  • 34
  • Thanks, Kishor. I'm not sure it's true that I'm overthinking this. I believe I understand the basic problem. And, really, it's a pretty simple problem. I just need something which will block other operations until my Meteor method has run. And it seems that asyncWrap will do that if I can just figure out how to implement it. I have read lots of different posts about asyncWrap. Now I just need to sit and work with it until I can get it to work. Just reading about it is not giving me a good understanding of it. – Darwood Horace Mar 25 '16 at 22:10
0

Everything I have read about scope in Meteor tells me that using the 'var' keyword in front of a variable declaration will give that variable 'file' scope whereas omitting the 'var' keyword will give it 'package' scope.

Using var makes it a local variable to that file, omitting var makes it a global variable if you're writing a Meteor application, and a package local variable if you're writing a Meteor package. If you're writing a Meteor package you can also specify that some of the local package variables should be global variables to the Meteor applications using the package (you can specify this in the package.js file).

I don't think that's the whole story, though. I think that the only time variables have true 'file' scope is when they are declared outside of any brackets. Otherwise, their scope seems to be limited to within those brackets.

No, nothing magic is going on here. Ordinary JavaScript is used, but each of your JavaScript file will be wrapped with (function(){ <your-code-here> })(). This is what creates the "file scope" (functions is the only way to create scopes in JavaScript before ES2015).

Your observed behavior is expected. If you find this strange, you probably haven't understood the difference between synchronous and asynchronous. JavaScript is single threaded, so if everything would happen synchronous, all timers and communication with the server done through JavaScript would freeze/pause your JavaScript code. To avoid this, we're forced to use asynchronous calls. This is true for all frameworks making use of JavaScript, and is not limited to Meteor applications.

A callback is a function that will get called some time in the future, when a result is ready (for example when we've got the result back from the server after we've sent a request to it). That's why your outer console.log is executed before your inner console.log.

Using a lot of callbacks can make your code hard to read. In Node JS, they added the future package, which you can use to use asynchronous functions as if they were synchronous. However, this functionality can't be implemented in pure JavaScript, so you can't use it on the client side, where all your code must be JavaScript code. This is really sad, but in Meteor there's a nice solution to it: reactive variables!

I don't know if I've helped you or not with this answer, but feel free to ask follow up questions.

Peppe L-G
  • 7,351
  • 2
  • 25
  • 50
  • Thanks, @Peppe. I understand the difference between synchronous and asynchronous calls. My confusion is around choosing the best method for blocking the variable assignment until the information is available from the method call. Between AJAX, wrapAsync, futures and one or two other features I've read about, I don't know which is the simplest and most straightforward way to go. This is something I'll need to be doing all the time, so I'd prefer to not have to learn it over and over again. -- I'm intrigued as to how reactive variables could help. Do you mean Session variables? – Darwood Horace Mar 28 '16 at 19:26
  • @DarwoodHorace, In JavaScript, there's no good way of blocking asynchronous calls (on the client). Your only option is to write the assignment statement in the callback. I don't know if reactive variables can help you with what you're trying to achieve (yes, `Session` is a reactive variable (or rather a reactive dictionary)), but if your problem somehow is connected to the graphical user interface, they're probably your simplest solution in Meteor. But what's your original problem? The user has a button, clicking it calls a method on the server, and what should happen when you have the respon? – Peppe L-G Mar 29 '16 at 18:05
0

In meteor, inside brackets, the scope rules for defined variables are the same as any other javascript.

But outside of any brackets, variables defined with let, var, or const have file scope, and other variables (without qualifiers) have global scope.

dpatte
  • 69
  • 1
  • 7