views:

77

answers:

5

eg I have two concurrent AJAX requests, and I need the result from both to compute a third result. I'm using the Prototype library, so it might look something like this:

var r1 = new Ajax.Request(url1, ...);
var r2 = new Ajax.Request(url2, ...);

function on_both_requests_complete(resp1, resp2) {
   ...
}

One way would be to use polling, but I'm thinking there must be a better way.

Update: An acceptable solution must be free of race conditions.

+1  A: 

On the callback function of each request, set a boolean such as

request1Complete and request2Complete

and call on_both_requests_complete(resp1,resp2).

In the handler function, check to see if both booleans are set. If not, just return and fall out of the function. The callback functions should be serialized, in that they cannot happen simultaneously, so this should work. If they could happen in parallel, you would break on a race condition.

Stefan Kendall
How do you know that the callbacks will be called one after another, and their statements not interleaved?
allyourcode
The callbacks are serialized. The statements cannot be interleaved.
Stefan Kendall
"How do you know"? Is this part of the JS spec?
allyourcode
Google "Browser Javascript single threaded"
Stefan Kendall
When I did that, this was one of the top results: http://www.oreillynet.com/cs/user/view/cs_msg/81559 I'll keep looking to see if there's more information, but judging from that post, being single threaded is an implementation detail, not part of the language definition itself. If you find an authoritative source that says otherwise and include it in your answer, I'll be happy to upvote + accept.
allyourcode
Sure, it's not part of the language, as any fool could write a multithreaded JavaScript implementation and call it proof against my answer. Browsers (currently) use a single-threaded model, and there are many applications which *rely* on this fact. For this reason, this isn't likely to change. Aside from this, the OP could just make the ajax calls NON-asynchronous via jQuery or chaining the functions together, but this is the easier, more practical solution given his problem statement.
Stefan Kendall
A: 

Well, you have to remember that the JS implementation in browsers is not really concurrent, and use that to your advantage. So what you would want to do is in each handler check if the other has finished. Example in jQuery:

var other_done = false;
$.get('/one', function() {
  if (other_done) both_completed();
  other_done = true;
  alert('One!');
});
$.get('/two', function() {
  if (other_done) both_completed();
  other_done = true;
  alert('Two!');
});
function both_completed() {
  alert('Both!');
}
Max Shawabkeh
+1: For using jQuery :-) lol
ocdcoder
I think this is semantically incorrect in that both functions must know about one another. I think the better, more logical solution is to put the test in the both_completed method, or use some proxy method to do the check.
Stefan Kendall
Stefan, those are implementation details. The idea that each handler updates a flag/counter (directly or by calling a function) can be implemented in a thousand different ways, but this one is the easiest to understand from a quick glance.
Max Shawabkeh
Isn't there a race condition here? I want to avoid that too.
allyourcode
@allyourcode: Read my first sentence. JS is not concurrent in browsers, so there's no way that either of the handlers runs between the check and the assignment in the other one.
Max Shawabkeh
-1: For using jQuery :-(. He asked for Prototype.
Justin Johnson
@Justin: You do realize that the idea can easily be implemented in any framework whatsoever, as well as raw JS.
Max Shawabkeh
I should have added that this also does not satisfy the problem. There is no mechanism for maintaining response data and passing it to the final event handler.
Justin Johnson
Max, that may be the case for today's browsers, but does the language definition say that event handlers must not be executed concurrently? If so, I'd love to see that, as I consider that to the linchpin to this whole question.
allyourcode
A: 

You can use the concept where you set temporary variables and wait for the "last" request to go. To do this, you can have the two handle functions set the tmp vars to the return val and then call your "on_both_requests_complete" function.

var processed = false;
var r1 = new Ajax.Request(...);
var r2 = new Ajax.Request(...);

(function() {
 var data1;
 var data2;

 function handle_r1(data) {
   data1 = data;
   on_both_requests_complete(); 
 };

 function handle_r2(data) {
   data2 = data;
   on_both_requests_complete();
 };

 function on_both_requests_complete() {
  if ( (!data1 || !data2) || processed) {
    return;
  }
  processed = true;

  /* do something */
 };
}();
ocdcoder
If data1 = data and data2 = data both get executed before if (!data1 || !$data2), then on_both_requests_complete will get executed twice, which could be a problem.
allyourcode
okay, that is a race condition which could be handled, however those chances are slim and the condition is easily handled with a flag. I've updated my code to represent that flag :-)
ocdcoder
+1  A: 

This is how I would do it. The approach is a general one, which gives you more flexibility and reuse, and avoids coupling and the use of globals.

var makeEventHandler = function(eventMinimum, callback) {
    var data = [];
    var eventCount = 0;
    var eventIndex = -1;

    return function() {
        // Create a local copy to avoid issues with closure in the inner-most function
        var ei = ++eventIndex;
        return function() {
            // Convert arguments into an array
            data[ei] = Array.prototype.slice.call(arguments);

            // If the minimum event count has not be reached, return
            if ( ++eventCount < eventMinimum  ) {
                return;
            }

            // The minimum event count has been reached, execute the original callback
            callback(data);
        };
    };
};

General usage:

// Make a multiple event handler that will wait for 3 events
var multipleEventHandler = makeMultipleEventHandler(3, function(data) {
    // This is the callback that gets called after the third event
    console.log(data);
});

multipleEventHandler()(1,2,3);
var t = multipleEventHandler();
setTimeout(function() {t("some string");}, 1000);
multipleEventHandler()({a: 4, b: 5, c: 6});

Output from callback (condensed by Firebug):

 [[1, 2, 3], ["some string"], [Object { a=4,  more...}]]

Notice that the order of data in the final callback is in order of the calling events, even though the second "event" executes after the third.

To use this in context of your Ajax requests:

var onBothComplete = makeMultipleEventHandler(2, function(data) {
    // Do something
    ...
});
new Ajax.Request(url1, {onComplete: onBothComplete()});
new Ajax.Request(url2, {onComplete: onBothComplete()});

Edit: I've updated the function to force data to always maintain the asynchronously received event data in the synchronously executed order (the previous caveat no longer exists).

Justin Johnson
Wow. That was pretty difficult to grok! This seems to avoid race conditions as long as expression evaluation is not carried out concurrently. I think the linchpin in your implementation is the if condition ++eventCount < eventMinimum, which updates progress and checks to see if all the events have been fired in a single expression. Kind of reminds me of the test-and-set instruction, which is used to build locks. Based on your answer, I think I can come up with something a little simpler (again, assuming that expressions are not evaluated concurrently).
allyourcode
I'm glad it was helpful to whatever extend. Know that expression evaluation is not concurrent in JavaScript. The language is executed in a single thread and to date, there is no good reason to design for any other situation.
Justin Johnson
A: 

Based on Justin Johnson's response to this question:

function sync(delays /* Array of Functions */, on_complete /* Function */) {
    var complete_count = 0;
    var results = new Array(delays.length);

    delays.length.times(function (i) {
        function on_progress(result) {
            results[i] = result;
            if (++complete_count == delays.length) {
                on_complete(results);
            }
        }
        delays[i](on_progress);
    });
}

This assumes each delay accepts one argument: an "on progress" event handler, which takes one argument: the result that the delay is trying to compute. To complete the example in my original question, you'd use it like so:

var delays = [];
delays[0] = function (on_progress) {
    new Ajax.Request(url1, {onSuccess: on_progress});
};
delays[1] = function (on_progress) {
    new Ajax.Request(url2, {onSuccess: on_progress});
};
function on_complete(results) { alert(results.inspect()); }
sync(delays, on_complete);

The one thing I'm not sure of is whether this avoids a race condition. If the expression ++complete_count == delays.length always happens atomically, then this should work.

allyourcode
Expressions, and maybe even entire functions are atomic in javascript.
Justin Johnson
Actually a discussion of what is atomic in JavaScript seems irrelevant. There's a good discussions starting here: http://www.techlists.org/archives/web/javascript/2006-05/msg00000.shtml Also, try this code so you can convince/see for yourself f1 = function() {for ( var i=0; i<100; i++ ) console.log("f1 %s", i); f2();};f2 = function() { for ( var i=0; i<100; i++ ) console.log("f1 %s", i); };setTimeout(function() { console.warn("async +0.025s"); }, 25);f1();
Justin Johnson
Justin, what's the point the code you left in your comment? I ran it, and I understand it, but I'm not sure what I'm supposed to be convinced of.
allyourcode