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');
}