Javascript - synchronizing after asynchronous calls

oldNoakes picture oldNoakes · May 13, 2009 · Viewed 19.4k times · Source

I have a Javascript object that requires 2 calls out to an external server to build its contents and do anything meaningful. The object is built such that instantiating an instance of it will automatically make these 2 calls. The 2 calls share a common callback function that operates on the returned data and then calls another method. The problem is that the next method should not be called until both methods return. Here is the code as I have implemented it currently:

foo.bar.Object = function() {
this.currentCallbacks = 0;
this.expectedCallbacks = 2;

this.function1 = function() {
    // do stuff
    var me = this;
    foo.bar.sendRequest(new RequestObject, function(resp) {
        me.commonCallback(resp);
        });
};

this.function2 = function() {
    // do stuff
    var me = this;
    foo.bar.sendRequest(new RequestObject, function(resp) {
        me.commonCallback(resp);
        });
};

this.commonCallback = function(resp) {
    this.currentCallbacks++;
    // do stuff
    if (this.currentCallbacks == this.expectedCallbacks) {
        // call new method
    }
};

this.function1();
this.function2();
}

As you can see, I am forcing the object to continue after both calls have returned using a simple counter to validate they have both returned. This works but seems like a really poor implementation. I have only worked with Javascript for a few weeks now and am wondering if there is a better method for doing the same thing that I have yet to stumble upon.

Thanks for any and all help.

Answer

Bryan Kyle picture Bryan Kyle · May 13, 2009

Unless you're willing to serialize the AJAX there is no other way that I can think of to do what you're proposing. That being said, I think what you have is fairly good, but you might want to clean up the structure a bit to not litter the object you're creating with initialization data.

Here is a function that might help you:

function gate(fn, number_of_calls_before_opening) {
    return function() {
        arguments.callee._call_count = (arguments.callee._call_count || 0) + 1;
        if (arguments.callee._call_count >= number_of_calls_before_opening)
            fn.apply(null, arguments);
    };
}

This function is what's known as a higher-order function - a function that takes functions as arguments. This particular function returns a function that calls the passed function when it has been called number_of_calls_before_opening times. For example:

var f = gate(function(arg) { alert(arg); }, 2);
f('hello');
f('world'); // An alert will popup for this call.

You could make use of this as your callback method:

foo.bar = function() {
    var callback = gate(this.method, 2);
    sendAjax(new Request(), callback);
    sendAjax(new Request(), callback);
}

The second callback, whichever it is will ensure that method is called. But this leads to another problem: the gate function calls the passed function without any context, meaning this will refer to the global object, not the object that you are constructing. There are several ways to get around this: You can either close-over this by aliasing it to me or self. Or you can create another higher order function that does just that.

Here's what the first case would look like:

foo.bar = function() {
    var me = this;        
    var callback = gate(function(a,b,c) { me.method(a,b,c); }, 2);

    sendAjax(new Request(), callback);
    sendAjax(new Request(), callback);
}

In the latter case, the other higher order function would be something like the following:

function bind_context(context, fn) {
    return function() {
        return fn.apply(context, arguments);
    };
}

This function returns a function that calls the passed function in the passed context. An example of it would be as follows:

var obj = {};
var func = function(name) { this.name = name; };
var method = bind_context(obj, func);
method('Your Name!');
alert(obj.name); // Your Name!

To put it in perspective, your code would look as follows:

foo.bar = function() {
    var callback = gate(bind_context(this, this.method), 2);

    sendAjax(new Request(), callback);
    sendAjax(new Request(), callback);
}

In any case, once you've made these refactorings you will have cleared up the object being constructed of all its members that are only needed for initialization.