Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Javascript - synchronizing after asynchronous calls

Tags:

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.

like image 305
oldNoakes Avatar asked May 13 '09 03:05

oldNoakes


People also ask

How does JavaScript handle asynchronous calls?

JavaScript provides three methods of handling asynchronous code: callbacks, which allow you to provide functions to call once the asynchronous method has finished running; promises, which allow you to chain methods together; and async/await keywords, which are just some syntactic sugar over promises.

Does JavaScript run synchronous or asynchronous?

JavaScript is Synchronous Spoiler: at its base, JavaScript is a synchronous, blocking, single-threaded language. That just means that only one operation can be in progress at a time.

Is async await synchronous JavaScript?

Async/await helps you write synchronous-looking JavaScript code that works asynchronously. Await is in an async function to ensure that all promises that are returned in the function are synchronized. With async/await, there's no use of callbacks.

Does JavaScript execute asynchronously?

JavaScript is only asynchronous in the sense that it can make, for example, Ajax calls. The Ajax call will stop executing and other code will be able to execute until the call returns (successfully or otherwise), at which point the callback will run synchronously.


2 Answers

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.

like image 71
Bryan Kyle Avatar answered Nov 15 '22 12:11

Bryan Kyle


I can add that Underscore.js has a nice little helper for this:

Creates a version of the function that will only be run after first being called count times. Useful for grouping asynchronous responses, where you want to be sure that all the async calls have finished, before proceeding.

_.after(count, function)

The code for _after (as-of version 1.5.0):

_.after = function(times, func) {
  return function() {
    if (--times < 1) {
      return func.apply(this, arguments);
    }
  };
};

The license info (as-of version 1.5.0)

like image 37
dani Avatar answered Nov 15 '22 12:11

dani