0

I have created a web api method that carries out calculations using a json object I post to the method. My understanding is that a jquery post is asynchronous? Assuming this is so, I'd like to be able to chain together multiple calls to the js function that invokes this api method because certain calls are order-critical.

There are 80 calls to this asynchronous api method in a row. I don't want to do:

asyncMethodCall1(myParams).then(asyncMethodCall2(myParams).then...)) 
...

as there is no need to chain them like this as none of them depend on the other and can all run simultaneously. However at the end I need to do some other calculations that DO depend on the previous results having finished.

Is there a way of doing a sort of group of calls followed by a "then"? Is it simply a case of having something like:

mySynchronousFunction(params).then(function() {...do other calcs});

function mySynchronousFunction(params) {
    asyncmethodcall1(myparams);
    asyncmethodcall2(myparams);
    asyncmethodcall3(myparams);
    ...
    asyncmethodcall76(myparams);
}

or do I need to have a "setTimeOut" in there somewhere? JavaScript is something I'm tryign to learn and it's all a bit odd to me at the moment!

EDIT 1

Well I'm stumped.

I know it's a fundamental lack of understanding on my part that's causing the problem but it's very hard to find basic introductory stuff that someone coming from a synchronous language can follow and understand. Currently a resource I'm working through is this one and it seems to make some sense but it's still not sinking in: http://blog.mediumequalsmessage.com/promise-deferred-objects-in-javascript-pt1-theory-and-semantics

Currently I have this:

        $.when(
            individualImpactCalc('byWeightAndFactor', 'CO2e', articleResults().material_CO2e),
            individualImpactCalc('byWeightAndFactor', 'Water', articleResults().material_Water),
            individualImpactCalc('byWeightAndFactor', 'pH', articleResults().material_pH),

        ...lots of other calls in here...

        ).then(function () {
            //do calculation totalling
            alert("I'm done!");
        }).fail(function() {
            alert("Argh! I failed!");
        });

...and it simply doesn't work as I want it to. I never get an alert showing. The impact calculations are done, the observables are updated, my page values change, but no alerts.

What am I fundamentally missing here?

Edit 2

What I was fundamentally missing was the difficulty in debugging chained promises! It actually was working once I cured a hidden reference error that wasn't bubbling up. Lots of painstaking stepping through javascript finally found it.

Just to add in another element to the mix of answers, I used the Q library as that was what is used in Durandal/Breeze anyway and it's just easy to keep the code consistent. I tried it with $.when and it worked just as well. I tried it with Promise and it failed with "Promise is not defined".

My working Q implementation:

        var calcPromise = Q.all([
            individualImpactCalc('byWeightAndFactor', 'CO2e', articleResults().material_CO2e),
            individualImpactCalc('byWeightAndFactor', 'Water', articleResults().material_Water),
            individualImpactCalc('byWeightAndFactor', 'pH', )]);

        return calcPromise
            .then(calcsDone)
            .fail(calcsFailed);

        function calcsDone() {
            alert("all calcs done");
        }
        function calcsFailed() {
            alert("Argh! Calc failure...");
        }
TheMook
  • 1,531
  • 4
  • 20
  • 58
  • Javascript in the browser is single threaded. So, there is no inherent value in changing your code to do a group of calls "together" since they'll still run one after the other. – Chandranshu Nov 12 '13 at 10:37
  • 1
    Note that you cannot do `then(asyncMethodCall2(myParams))` because that executes asyncMethodCall2 before then() itself and before previous calls are executed. You must use `then(function(){return asyncMethodCall2(myParams)})`. Don't forget the return, otherwise the promise returned by asyncMethodCall2 isn't chained further. – djk Nov 12 '13 at 10:53
  • It's all so confusing! I need to go and read the link that Barmar posted. Thanks. – TheMook Nov 12 '13 at 11:10
  • OP updated under "edit 1" – TheMook Nov 13 '13 at 22:09

3 Answers3

1

What you are looking for is the jQuery when method: http://api.jquery.com/jQuery.when/

In the case where multiple Deferred objects are passed to jQuery.when, the method returns the Promise from a new "master" Deferred object that tracks the aggregate state of all the Deferreds it has been passed. The method will resolve its master Deferred as soon as all the Deferreds resolve, or reject the master Deferred as soon as one of the Deferreds is rejected.

Specifically you put the calls that don't depend on each other in when, and the ones that depend on them in the then.

$.when(/* independent calls */).then(/* dependent calls */);

So as an example if you want to run deffered 1 and 2 in paralel, then run 3, then run 4 you can do:

$.when(def1, def2).then(def3).then(def4);
Tibos
  • 27,507
  • 4
  • 50
  • 64
1

You can use Promise.when() to wait for all of them.

function mySynchronousFunction(params) {
    return Promise.when(asyncmethodcall1(myparams),
                        asyncmethodcall2(myparams),
                        ...);
}

Promise.when returns a new promise, so you can chain this with .then().

See Asynchronous Programming in JavaScript with “Promises”

Barmar
  • 741,623
  • 53
  • 500
  • 612
  • What's the difference (if any) between "$when(..." and "Promise.when(..." ? – TheMook Nov 12 '13 at 11:11
  • With `Promise.when` Barmar refers to [CommonJS](http://wiki.commonjs.org/wiki/Promises/A), not jQuery. See his link: "...let’s start out with the CommonJS Promise/A proposal..." – djk Nov 13 '13 at 10:01
  • @CarlEdwards There was no mention of jQuery in the question when I answered yesterday. Someone added it to the tags, although I don't see the justification, except that you accepted the solution that uses `$.when` (I hope you'll remember the `.` when you apply it in your code, since you mistyped it as `$when` twice in your comments). – Barmar Nov 13 '13 at 15:55
  • Hi Barmar. I haven't accepted any solution on here yet? I'm still wading through the different answers and trying them out. I'm struggling to get it working, but I think that's because the code is within the Durandal framework and that already has certain restrictions with deferred, using the Q library I believe? I'm still trying different versions of the code now to see if I can get it working. – TheMook Nov 13 '13 at 18:14
  • I thought I saw the checkmark next to Doug's answer when I looked earlier. – Barmar Nov 13 '13 at 18:20
  • @Barmar there is a reference to jQuery post request, that is why i based my answer in jQuery. Fundamentally the answers are the same, no matter what particular implementation of promises is used (ex: the OP using Q in the end). – Tibos Nov 14 '13 at 09:57
1

As others have pointed out, you can use $.when(...) to call all of the methods at once and only continue with .then() when all are done. But that doesn't solve your problem, because the methods are started all at once and there is no chaining. Calls to your web api can still come in and finish in random order.

Even though your example shows different async methods, your description sounds like you have only one method with different parameter values. So why not collecting those parameters in an array and then chaining the calls in a loop?

var params = [ 27, 38, 46, 83, 29, 22 ];

var promise = asyncmethodcall(params[0]);
for (var i=1; i<params.length; i++) {
    promise = promise.then(buildAsyncmethodcall(params[i]));
}

function buildAsyncmethodcall(param) {
    return function() {
        return asyncmethodcall(param);
    }
}

http://jsfiddle.net/qKS5e/ -- see here why I had to build the function using another function

If you really want to call different methods, you could write a jQuery plugin eg. $.whenSequential(...) to which you pass an array of functions that return Deferred objects like this:

$.whenSequential( // your cool new plugin
    function() { return callasyncmethod1(123); },
    function() { return callasyncmethod2(345); },
    function() { return callasyncmethod3(678); }
);

The plugin method would work just like the for-loop above, but instead of params you iterate the function arguments and pass one by one to then().

Either way, you won't get around wrapping all calls in a function somehow to chain them, because otherwise they would get executed immediately (like with $.when).

Community
  • 1
  • 1
djk
  • 943
  • 2
  • 9
  • 27
  • I don't mind all the function calls being simultaneous though! The 76 individual calls can happily be set off at the same time, the only restriction is on the last bits that I need to do when the 76 calls are complete. I think the $when solution does seem to offer the best solution here, but I appreciate your detailed input, thanks! – TheMook Nov 12 '13 at 15:37