Dealing with Asynchronous JavaScript

Wednesday, June 24, 2009 | 4:24 PM

Labels:

I'm sure many of you reading this are JavaScript experts but I'm sure some of you, like me, might have lots of graphics experience with C or C++ but not so much with JavaScript. This post is about one of the things that is different about JavaScript that I needed to learn as an experienced programmer in other languages.

First, I highly recommend you get the book, "JavaScript: The Good Parts" by Douglas Crockford. It's a book that really changed my mind about JavaScript and has showed me how to use it much more effectively.

As O3D is a JavaScript based API for the browser, there are many times when one needs to do asynchronous calls to some function. A good example is when you load a 3d scene. You call loadScene, and when the scene has finished loading some moments later, it calls a callback you specify. In C++, these types of functions usually have a parameter for you to pass in some kind of user data that will then be passed to your callback. That way you can associate something with each call. JavaScript APIs don't generally have that user data concept. The reason is, they don't need it. Here's why:

First off, unlike C or C++, JavaScript does not define scope with braces. To see what I mean check out the following code.

function someFunc() {
{
var foo = 123;
}
{
console.log("foo = " + foo);
}
};

To a C++ programmer that looks like it has 3 scopes. The scope of the function, "someFunc", the scope surrounding the declaration of "foo", and the scope printing the result. (*) As such, in C++ that code would fail to compile because foo does not exist when it gets to the line to print it. In JavaScript though this is perfectly valid code because, as I mentioned, JavaScript does not use braces to define scope.

Instead, JavaScript uses functions to define scope so foo is visible inside someFunc but it is not visible outside of someFunc.

Functions can see the scopes above them so for example look at the code below:
function someFunc() {

var foo = 123;

function innerFunc() {
console.log("foo = " + foo);
}

innerFunc();
};

In this code, someFunc has an inner function called "innerFunc" and innerFunc can see the variable foo. Pretty straight forward.

Here is where the magic comes in and points out a big difference between JavaScript and C++.
function someFunc() {
var foo = 123;

function innerFunc() {
console.log("foo = " + foo);
}

return innerFunc;
}

This version of someFunc does not print anything. Instead it returns a function that prints something. To see it print, you have to call the function it returns. If you look at the code you'll see innerFunc prints the value of "foo" but foo is declared outside of its scope. If you tried this in C++, assuming you could make it compile, your function would crash because foo would get destroyed when someFunc returned. JavaScript on the other hand doesn't work like that. This code will work just fine. You would use it like this:
var functionReturnedBySomeFunc = someFunc();

// call the function returned by someFunc
functionReturnedBySomeFunc();

someFunc returned a function which is stored in a variable and then that function is called.

What is this good for? Well, it's great for asynchronous callbacks like o3djs.scene.loadScene because the callback you pass in will retain knowledge of the things that were in its scope when it was defined. For example:
// A constructor for a MyObject
function MyObject(client, viewInfo) {
this.client = client;
this.viewInfo = viewInfo;
this.pack = client.createPack();
this.transform = this.pack.createObject('Transform');
};

MyObject.prototype.loadScene = function(url) {
// declare a local variable so inner functions can see it.
var that = this;

// An inner function that loadScene will call on completion.
function loadCallback(pack, parent, exception) {
if (!exception) {
// you can reference the particular MyObject through
// the "that" variable implicitly.
that.initScene();
}
}

// load the scene.
o3djs.scene.loadScene(
this.client,
this.pack,
this.transform,
url,
loadCallback);
};

MyObject.prototype.initScene = function() {
o3djs.pack.preparePack(this.pack, this.viewInfo);
};

You can use MyObject like this:
var obj = new MyObject(client, viewInfo);
obj.loadScene('http://someplace.com/somemodel.o3dtgz');

As you can hopefully see, unlike C++, we didn't need a special user data parameter to make loadScene useful. JavaScript makes it easy for us to allow the callback to have as much or as little access to other data as it needs.

If you're new to JavaScript I hope that's a useful example. It can take a little while to get used to a new language but every language has its strengths.

(*) console.log is something that only works on Safari, Firefox and Chrome when the debugger is running. It is non-standard JavaScript but helped to make these examples clearer.

2 comments:

Axel said...

Slightly off topic, but you'll be happy to know that C++ does provide closures through the newer lambda functions of the c++0x standard. Now, whether or not they will crash depends on what you do with them. It's c++ after all, so if you pass a reference to your value to an out-of-scope, then you'll crash (if you're lucky). But you can specify closures-by-value too. The power stays in your hands

Sai Sasidhar Maddali said...

I am on the way to create a technical WEB for our college which includes downloading STUDY MATERIAL in variable formats.I want to display a ALERT to the users who click on the link saying "thank you & study well with this materials" and then start download after clicking ok in that alert dialog box.I tried that in many ways developing my own script but of no use so i would like you to help me in this problem.. mailto: sasidhar1237@yahoo.co.in THANKS IN ADVANCE.