0

I have a simple object that works some of the time but not others and I am trying to figure out why:

var Language = {
    language:"fr",

    words:{
    },

    setLanguage: function(lang) {
        Language.language=lang;
    },

    loadFile: function(file) {
        //get the json object
        $.getJSON("/" + Language.language + "/lang/" + file, function( data ) {
            //go through each item in the json object and add it to the 
            $.each( data, function( key, val ) {
                console.log(key+":"+val);
                Language.words[key]=val;                //add the word
            });
        });
    },

    getWord: function(word) {
        return Language.words[word];
    }   
};

It seems the order of execution is random. In the below 2 examples I see the alert then the console populates.

1)

    <script src="/common/js/jquery.min.js"></script>                                    
    <script src="/common/js/language.js"></script>                                      

    <script>
        $(document).ready(function(){ 
            Language.setLanguage("en"); 
            Language.loadFile("main.json");

            alert(Language.getWord('lang')); 
        });
    </script>

2)

    <script src="/common/js/jquery.min.js"></script>    
    <script src="/common/js/language.js"></script>  
    <script>
        Language.setLanguage("en"); 
        Language.loadFile("main.json");

        $(document).ready(function(){ 
            alert(Language.getWord('lang')); 
        });
    </script>

I have not been able to reproduce of late but I have gotten "English" to come back once which is the value found in the JSON file with index lang. How can I make sure the object gets populated at the start of my script so any other scripts that are loaded can use it.

Ken Y-N
  • 14,644
  • 21
  • 71
  • 114
  • All the info you need to know is here: http://stackoverflow.com/questions/14220321/how-do-i-return-the-response-from-an-asynchronous-call It isn't necessarily a duplicate because of the phrasing, but it's the same issue. There are thousands of others. You need to learn about *asynchronous* code. –  Jan 22 '16 at 03:41
  • Here's a hint... see that callback function you're passing to `$.getJSON()`? It's there for a reason. As you can see, the data is available reliably inside, so... –  Jan 22 '16 at 03:42
  • silly me. Is there a way to load a json file non asyncronously? It should be cached as it is a static file. – Matthew Cornelisse Jan 22 '16 at 03:43
  • Yes, but you don't want to. You need to become comfortable with passing around callback functions to deal with async code. –  Jan 22 '16 at 03:44
  • You should store a promise for the words and use that everywhere. – Bergi Jan 22 '16 at 03:45
  • So you are sugesting my best aproach is keep the asyncronous call but set a flag to stall the code if it has not returned yet to prevent getting undefined back? – Matthew Cornelisse Jan 22 '16 at 03:47
  • You *never* want to stall code in a browser. It freezes the entire thing. Define a second parameter to your `loadFile()` method, and pass a function to it that does whatever you want with the result. Then invoke that function after your `$.each()` loop. –  Jan 22 '16 at 03:48
  • the problem in this case is the words are used at random based on user events. Mostly error messages in the users language. there are hundreads of functions that may need to get called. Though good chance the asyncronous call will have loaded before any human responds to anything to push a link. Especially since I have caching set by the server to 1 year – Matthew Cornelisse Jan 22 '16 at 03:51
  • So if basically all your application code relies on this single request, you may just need to put all that code in a function and have that function invoked when the data arrives. That way none of it runs before the words are defined. –  Jan 22 '16 at 03:55
  • @squint So we're resurrecting callback-style programming now? Anyway, I think it **is** a dup of the question you mention and am tempted to close it as such. –  Jan 22 '16 at 03:58
  • @torazaburo: It never died. –  Jan 22 '16 at 03:58
  • @torazaburo: Close it if you think it's a dupe. I sure won't object. I wasn't sure because it's not really about returning. Just a subtle difference. –  Jan 22 '16 at 04:02
  • 1
    @torazaburo The op isn't confortable with callbacks yet, and you want him to understand promises, just let him go step by step, and as squint said, callbacks never died. – Marcos Casagrande Jan 22 '16 at 04:15
  • Actually, he's **already** using promises, since `$.getJSON` returns a sort of promise. He's just not aware of it, and throwing it away. Anyway, I don't think learning a semi-obsolete pattern is a prerequisite for learning a newer one. –  Jan 22 '16 at 04:41

2 Answers2

1

We find ourselves in a universe which appears to progress along a dimension we call "time". We don't really understand what time is, but we have developed abstractions and vocabulary that let us reason and talk about it: "past", "present", "future", "before", "after".

The computer systems we build--more and more--have time as an important dimension. Certain things are set up to happen in the future. Then other things need to happen after those first things eventually occur. This is the basic notion called "asynchronicity". In our increasingly networked world, the most common case of asynchonicity is waiting for some remote system to response to some request.

Consider an example. You call the milkman and order some milk. When it comes, you want to put it in your coffee. You can't put the milk in your coffee right now, because it is not here yet. You have to wait for it to come before putting it in your coffee. In other words, the following won't work:

var milk = order_milk();
put_in_coffee(milk);

Because JS has no way to know that it needs to wait for order_milk to finish before it executes put_in_coffee. In other words, it does not know that order_milk is asynchronous--is something that is not going to result in milk until some future time. JS, and other declarative languages, execute one statement after another without waiting.

The classic JS approach to this problem, taking advantage of the fact that JS supports functions as first-class objects which can be passed around, is to pass a function as a parameter to the asynchonous request, which it will then invoke when it has complete its task sometime in the future. That is the "callback" approach. It looks like this:

order_milk(put_in_coffee);

order_milk kicks off, orders the milk, then, when and only when it arrives, it invokes put_in_coffee.

The problem with this callback approach is that it can rapidly become unwieldy when dealing with longer sequences of events. For example, let's say that I want to wait for the milk to be put in the coffee, and then and only then perform a third step, namely drinking the coffee. I end up needing to write something like this:

order_milk(function(milk) { put_in_coffee(milk, drink_coffee); }

where I am passing to put_in_coffee both the milk to put in it, and also the action (drink_coffee) to execute once the milk has been put in. Such code becomes hard to write, and read, and debug.

Marcos has already provided an example of how to use the callback pattern for your particular problem.

This was the motivation for the notion of a "promise", which is a particular type of value which represents a future or asynchronous outcome of some sort. It can represent something that already happened, or that is going to happen in the future, or might never happen at all. Promises have a single method, named then, to which you pass an action to be executed when the outcome the promise represents has been realized.

In the case of our milk and coffee, we design order_milk to return a promise for the milk arriving, then specify put_in_coffee as a then action, as follows:

order_milk() . then(put_in_coffee)

One advantage of this is that we can string these together to create sequences of future occurrences ("chaining"):

order_milk() . then(put_in_coffee) . then(drink_coffee)

Let's apply promises to your particular problem. We will modify loadFile to return a promise:

loadFile: function(file) {
    //get the json object
    return $.getJSON("/" + Language.language + "/lang/" + file, function( data ) {
        //go through each item in the json object and add it to the 
        $.each( data, function( key, val ) {
            console.log(key+":"+val);
            Language.words[key]=val;                //add the word
        });
    });
},

Actually, all we've done is added a return to the call to $.getJSON. This works because jQuery's $.getJSON already returns a kind of promise-like thing. (In practice, without getting into details, we would prefer to wrap this call so as return a real promise, or use some alternative to $.getJSON that does so.) Now, if we want to load the file and wait for it to finish and then do something, we can simply say

Language.loadFile() . then(do_something)

for instance,

Language.loadFile() . 
  then(function() { alert(Language.getWord('lang')); });

When using promises, we end up passing lots of functions into then, so it's often helpful to use the more compact ES6-style arrow functions:

Language.loadFile() . then(() => alert(Language.getWord('lang')));

But there's still something vaguely dissatisfying about having to write code one way if synchronous and a quite different way if asynchronous. For synchronous, we write

a();
b();

but if a is asynchronous, with promises we have to write

a() . then(b);

Above, we said "JS has no way to know that it needs to wait for the first call to finish before it executes the second". Wouldn't it be nice if there was some way to tell JS that. It turns out that there is--the await keyword, used inside a special type of function called an "async" function. This feature is part of the upcoming version of ES, but is already available in transpilers such as Babel given the right presets. This allows us to simply write

async function morning_routine() {
  var milk   = await order_milk();
  var coffee = await put_in_coffee(milk);
  await drink(coffee);
}

In your case, you would be able to write something like

async function foo() {
  await Language.loadFile("main.json");
  Language.getWord('lang');
}
  • And the questions title is *"trying to make object initialize first in javascript"* I wonder how many people having an issue with asynchronous programming is going to find this question (as it is). I suggest posting such answers to a canonical question, if no such thing exist, create, self answer and mark this as duplicate so that it finds future readers. – T J Jan 22 '16 at 05:17
  • `order_milk(function(milk) { put_in_coffee(milk, drink_coffee); }` - bad example. You should explicitly mention that `put_in_coffee` performs an asynchronous action, like you'll drink coffee only if your wife comes and puts it in coffee for you at a later point in time. This kind of examples makes newbies think js transfers control to another function only after executing everything in the current one. In other words if you can put the milk in coffee yourself, there is no need for a callback to drink it. – T J Jan 22 '16 at 05:29
  • @TJ Thanks for the input. –  Jan 22 '16 at 05:31
  • Thank you for the in-depth and helpful answer. – Matthew Cornelisse Jan 22 '16 at 06:29
0

The problem is that $.getJSON is executed asynchronously, that's why you're getting that behaviour.

Pass a callback to your loadFile function, and use the rest of the code there.

language.js

loadFile: function(file, callback) {
   //get the json object
   $.getJSON("/" + Language.language + "/lang/" + file, function( data ) {
       //go through each item in the json object and add it to the 
       $.each( data, function( key, val ) {
           console.log(key+":"+val);
           Language.words[key]=val;                //add the word
       });
       callback(); //Callback function
   });
},

HTML

 <script>
        $(document).ready(function(){ 
            Language.setLanguage("en"); 
            Language.loadFile("main.json", function(){
               alert(Language.getWord('lang')); 
            });     
        });
 </script>
Marcos Casagrande
  • 37,983
  • 8
  • 84
  • 98
  • i guess it is pretty much the same as using document ready. call my function words ready. Thanks. – Matthew Cornelisse Jan 22 '16 at 04:02
  • If you want to do dom manipulation, I'll put the code within the document ready, because it can happen that your loadJson finishes before your document ready, and other things could go wrong. – Marcos Casagrande Jan 22 '16 at 04:05