JavaScript: variables in asynchronous callback functions

What’s the problem?

First let’s assume you have declared a variable called myVar:

var myVar;

If you immediately log the contents of the variable, it will return undefined.

Now if you define any of the following:

setTimeout(function() { myVar = "some value"; }, 0);

or:

$.ajax({
	url: '...',
	success: function(response) {
		myVar = response;
	}
});

or:

var script = document.createElement("script");
script.type = "text/javascript";
script.src = "myscript.js";
script.onload = function(){
	myVar = "some value";
};
document.body.appendChild(script);

and immediately afterwards display the contents of myVar, you will also get undefined as result.

Why is the variable not set?

In all examples above, the function defined are what’s called asynchronous callbacks. This means that they are immediately created but not immediately executed. setTimeout pushes it into the event queue, the AJAX calls will execute it once the call returns and onload will be executed when the DOM element is loaded.

In all three cases, the function does not execute right away but rather whenever it is triggered asynchronously. So when you log or display the contents of the variable, the function has not yet run and the content has not yet been set.

So even though JavaScript actually works with a single-thread model, you’ve landed in the asynchronous problem zone.

Synchronous vs. Asynchronous

Basically the difference between a synchronous function call and an asynchronous one can be shown with these 2 pieces of code:

var myVar;

function doSomething() {
	myVar = "some value";
}

//synchronous call
doSomething();

log(myVar);

And:

var myVar;

function doSomething() {
	myVar = "some value";
}

log(myVar);

//asynchronous call
doSomething();

In the asynchronous case, our function is also defined before logging the variable but is call some time later.

How to make it work?

You will need to rewrite your code in order to use callbacks which will be called when the processing (in this case setting the value of myVar) is done.

First example: Instead of using the following:

var myVar;

setTimeout(function() { 
    myVar = "some value"; 
}, 0);

alert(myVar);

You should rather do the following:

var myVar;

function callback() {
    alert(myVar);
}

setTimeout(function() { 
    myVar = "some value"; 
    callback();
}, 0);

Instead of this:

var myVar;

var script = document.createElement("script");
script.type = "text/javascript";
script.src = "d3.min.js";
script.onload = function(){
	myVar = "some value";
};
document.body.appendChild(script);

alert(myVar);

Use this:

function callback(response) {
    alert("loaded");
}

var script = document.createElement("script");
script.type = "text/javascript";
script.src = "d3.min.js";
script.onload = callback;
document.body.appendChild(script);

And instead of doing the following:

var myVar;

$.ajax({
	url: '...',
	success: function(response) {
		myVar = response;
	}
});

alert(myVar);

Do the following:

function callback(response) {
    alert(response);
}

$.ajax({
	url: '...',
	success: callback
});

Of course, here another solution is to make the AJAX call synchronous by adding the following to the options:

async: false

Also if you use $.get or $.post, you’ll need to rewrite it to use $.ajax instead.

But this is not a good idea. First if the call lasts longer than expected, you will block the UI making your browser window unresponsive. And at some point in the time the browser will ask your user whether you want to stop the unresponsive script. So, even though programing everything in an asynchronous way with callbacks is not always trivial, doing everything in a synchronous way will cause more sporadic and difficult to handle issues.

So to summarize, you should either use a callback or directly call a function after processing and not only rely on setting a variable in the asynchronous part.

Leave a Reply

Your email address will not be published. Required fields are marked *