3

What I'm Trying To Accomplish

I need to trigger 1 to 3 different $.post() requests, and whether it's 1 call, 2 consecutive calls, or 3 consecutive calls is decided by some basic user selection. Each call must only start after the previous is completely finished.

I am dealing with 3 simple behavior cases -- the user, being presented with "Checkbox 1," "Checkbox 2," and a "Continue" button," opts to

  1. Select nothing and then press "Continue" button, which makes an XHR call to '/remote.php',
  2. The user opts to only select "Checkbox 1" or "Checkbox 2," and then presses "Continue" button, which calls $.post() Function 1 that is bound to Checkbox 1, or $.post() Function 2 that is bound to Checkbox 2, and then makes an XHR call to '/remote.php',
  3. Or the user selects both Checkbox 1 + 2 and then presses Continue, which calls $.post() Function 1, then calls $.post() Function 2, and then makes an XHR call to '/remote.php'.

I need to make sure that the Continue-button $.post() function does not fire until the Checkbox-bound $.post() functions fire and complete.


The Problem

The problem is that if Checkbox 1 is selected and Checkbox 2 is selected, and then the Continue button is pressed, as I understand it, the loading order should be:

  1. Checkbox 1 bound $.post() request fires and completes, then
  2. Checkbox 2 bound $.post() request fires and completes, then
  3. Continue button bound $.post() fires, and pages is changed via AJAX at the end of the function tied to "Continue" button bound function.

So, where the result should look like:

XHR finished loading: POST "/cart.php?action=add&product_id=1280".
XHR finished loading: POST "/cart.php?action=add&product_id=1284". 
XHR finished loading: POST "/remote.php".

It instead often comes out like this:

XHR finished loading: POST "/cart.php?action=add&product_id=1280".
XHR finished loading: POST "/remote.php".
XHR finished loading: POST "/cart.php?action=add&product_id=1284". 

So when the page changes due to the AJAX at the end of the "last"/"Continue-button function, either neither of the Checkbox 1 or Checkbox 2 actions have taken place, or one of the two or both do register in the backend (ie, added to cart) but do not reflect in the AJAXified DOM as they should as the final AJAX fires and completes before the previous $.post() calls have completed.


My Code

The HTML

The HTML is basic:

<form method="post" action="#" onsubmit="newChooseShippingProvider(); return false;">
    <label for="delSigCheck" class="del-sig-text"><input id="delSigCheck" type="checkbox" onchange="addDelSigToCart();" title="Add Delivery Signature"></label>
    <label for="addInsCheck" class="ins-add-calc"><input id="addInsCheck" type="checkbox" onchange="addInsToCart();" title="Add Delivery Signature" data-ins-id="1284"></label>
    <input type="submit" value="Continue" class="btn Small">
</form>

The Javascript/jQuery

This is my latest--4th or 5th--attempt, and still does not work:

function addDelSigToCart() {
    $('#delSigCheck').toggleClass('checked');
}
function addInsToCart() {
    $('#addInsCheck').toggleClass('checked');
}
function newChooseShippingProvider() {
    var originalCheckout = ExpressCheckout.ChooseShippingProvider();
    if ($('.ShippingProviderList .radio span').hasClass('checked')) {
        var addInsCheck = $('#addInsCheck').hasClass('checked');
        var delSigCheck = $('#delSigCheck').hasClass('checked');
        var insId = $('#addInsCheck').attr('data-ins-id');
        var addDelSigUrl = '/cart.php?action=add&product_id=1280';
        var addInsUrl = '/cart.php?action=add&product_id=' + insId;
        if (delSigCheck && addInsCheck) {
            $.post(addDelSigUrl, function() {
                $.post(addInsUrl, function() {
                    originalCheckout;
                });
            });
        } else if (!delSigCheck && !addInsCheck) {
            originalCheckout;
        } else if (delSigCheck && !addInsCheck) {
            $.post(addDelSigUrl, function() {
                originalCheckout;
            });
        } else if (!delSigCheck && addInsCheck) {
            $.post(addInsUrl, function() {
                originalCheckout;
            });
        }
    } else {
        originalCheckout;
    }

What I've Tried

I've gone through several version of chaining the $.post() calls, but nothing seems to work consistently.

What I am using now and what seems to work the best for me with extensive testing is using setTimeout to chain the function with some delay, like this:

    ...
    if (delSigCheck && addInsCheck) {
        $.post(addDelSigUrl);
        setTimeout(function() {
            $.post(addInsUrl);
            setTimeout(function() {
                ExpressCheckout.ChooseShippingProvider();
            }, 1300);
        }, 1300);
    } else if ...

And this version above is what I'm using now, as it seems to give the most consistent results, seeing the scripts load typically as 1,2,3, followed by a DOM AJAXified with appropriate changes based on function 1 and 2. However, I don't think the setTimeout is working as even when I increase it to 5000 or 10000, the action is performed "instantaneously" and no delay takes place (at least certainly nothing close to 5-10 seconds).

I've also tried putting the functions inside $.post()'s success callback:

    ...
    if (delSigCheck && addInsCheck) {
        $.post(addDelSigUrl, function() {
            setTimeout(function() {
                $.post(addInsUrl, function() {
                    setTimeout(function() {
                        originalCheckout;
                    }, 1300);
                });
            }, 1300);
        });
    } else if ...

And finally I've also tried:

$.when($.post(addDelSigUrl)).then(originalCheckout);

as well as .done and success: but none of it works, and the $.posts()'s load in an unexpected order, failing.

The Question

  1. What am I doing wrong?
  2. How can I make it so 1 loads fully, then 2 loads fully, and only then 3 fires and loads?

UPDATE 1:

I just tried jfriend00's answer:

            $.post(addDelSigUrl, { cache: false }).then(function(data1) {
                //CONSOLE.LOGing HERE
                console.log(data1);
                return $.post(addInsUrl, { cache: false });
            }).then(function(data2) {
                //CONSOLE.LOGing HERE
                console.log(data2);
                return originalCheckout;
            });

But it still resulted in:

XHR finished loading: POST "/cart.php?action=add&product_id=1280".
XHR finished loading: POST "/remote.php".
XHR finished loading: POST "/cart.php?action=add&product_id=1284". 

and both console.logs fire immediately after the first "XHR ...", THEN /remote.php fires (though it should fire last as part of originalCheckout), THEN the 3rd XHR fires.

UPDATE 2

Now that we got the XHRs firing and loading in the correct order via .then(), the second part of the problem I am having is that the 3rd XHR to /remote.php updates the DOM via AJAX with data from the backend. Part of that data is the 1st and 2nd $.posts.

I think the 3rd AJAX call is firing and completing milliseconds before some action is taken on the backend via server-side PHP, and because of this more than 50% of the time, the DOM update via the 3rd AJAX call is missing the data from the 1st and/or 2nd call (most often the DOM changes include Checkbox 1/AJAX call 1, but not 2).

How can I fix this? I've tried setTimeout but it doesn't seem to work as even when I set it to like 30000, the 3rd AJAX fires as soon as the 1st/2nd complete.

Latest front-end code:

function newChooseShippingProvider() {
    if ($('.ShippingProviderList .radio span').hasClass('checked')) {
        var addInsCheck = $('#addInsCheck').hasClass('checked');
        var delSigCheck = $('#delSigCheck').hasClass('checked');
        var insId = $('#addInsCheck').attr('data-ins-id');
        var addDelSigUrl = '/cart.php?action=add&product_id=1280';
        var addInsUrl = '/cart.php?action=add&product_id=' + insId;
        if (delSigCheck && addInsCheck) {
            $.post(addDelSigUrl).then(function(data1) {
                return $.post(addInsUrl);
            }).then(function(data2) {
                return ExpressCheckout.ChooseShippingProvider();
            });
        } else if (!delSigCheck && !addInsCheck) {
            ExpressCheckout.ChooseShippingProvider();
        } else if (delSigCheck && !addInsCheck) {
            $.post(addDelSigUrl).then(function(data1) {
                return ExpressCheckout.ChooseShippingProvider();
            });
        } else if (!delSigCheck && addInsCheck) {
            $.post(addInsUrl).then(function(data1) {
                return ExpressCheckout.ChooseShippingProvider();
            });
        }
    } else {
        ExpressCheckout.ChooseShippingProvider();
    }
}
Community
  • 1
  • 1
Andre Bulatov
  • 1,090
  • 3
  • 16
  • 47
  • Contributing wouldn't hurt you. Feel free to masterfully reduce the size of the query without hindering the question. – Andre Bulatov Aug 22 '15 at 21:40
  • 1
    $post(url, your_callback_goes_here) – webduvet Aug 22 '15 at 21:48
  • whether this should not work ---- $.post(addDelSigUrl).done(function(){ $.post(addInsUrl).done( function(){ originalCheckout; }) }); – user4621032 Aug 22 '15 at 21:55
  • What are all these references to `originalCheckout;`? Do you mean to be using a function call `originalCheckout();`. – jfriend00 Aug 22 '15 at 22:03
  • @jfriend00 -- originalCheckout is just a variable in which a reference to a function is stored for ease. It's in the lists of `var`s, as `var originalCheckout = ExpressCheckout.ChooseShippingProvider();`. That function is the original function which must be triggered to "continue." All my efforts is basically hijacking that function and prepending my `$.post()s` to it. – Andre Bulatov Aug 22 '15 at 22:36
  • @webduvet, that's one of the methods I tried, `$.post(url, function() {$.post(url)) to no avail. @Yotam Omer and @user4621032, I tried both of those but same result. – Andre Bulatov Aug 22 '15 at 22:40

2 Answers2

4

The simplest way to sequence jQuery ajax operations is to use the built-in promises:

$.post(...).then(function(data1) {
    return $.post(...);
}).then(function(data2) {
    return $.post(...);
}).then(function(data3) {
    // everything done here
});

Working demo that shows you the precise sequencing: http://jsfiddle.net/jfriend00/zcfr2xy0/


OK, it appears that the problem is that you're doing this:

var originalCheckout = ExpressCheckout.ChooseShippingProvider();

And, then you think that sometime later, you can just do:

originalCheckout;

and that will somehow execute the former. That's not the case. Your ExpressCheckout.ChooseShippingProvider() function is executed immediately and the return result from executing that function is assigned to originalCheckout.

You simply can't do it that way. When you have () after a function name, that means to execute it NOW. I would suggest that you just replace all instances of originalCheckout; with ExpressCheckout.ChooseShippingProvider();.

jfriend00
  • 683,504
  • 96
  • 985
  • 979
  • Thanks, but it doesn't seem to work. Please see UPDATE 1 in my question. – Andre Bulatov Aug 22 '15 at 22:20
  • @AndreBulatov - What is `return originalCheckout;` in your new example? The code in my answer absolutely works for sequencing Ajax calls (I've used it many times) so there's something else wrong in your setup or the actual code you're running that has nothing to do with this particular suggestion. I think we need to see the actual code you used with my structure because there's something going on in your actual code that you haven't shown us. – jfriend00 Aug 22 '15 at 22:25
  • originalCheckout is just a variable in which a reference to a function is stored for ease. It's in the lists of `var`s, as `var originalCheckout = ExpressCheckout.ChooseShippingProvider();`. That function is the original function which must be triggered to "continue." All my efforts is basically hijacking that function and prepending my `$.post()s` to it. – Andre Bulatov Aug 22 '15 at 22:36
  • 1
    @AndreBulatov - that's your problem. When you assign `originalCheckout`, the function is running at that moment. You can't do it that way. I will put a code suggestion in my answer. – jfriend00 Aug 22 '15 at 22:41
  • Your JSFiddle works perfectly. I tried it on my site and it doesn't work. I noticed there is obviously a major difference between your code and mine -- var data = .... Can you please explain the purpose of 'stringify JASON echo/delay: 1'. Do you think I need this also? – Andre Bulatov Aug 22 '15 at 23:01
  • okay, I get that you're passing `data` as a parameter to the `$.post()`. Do you think I need to pass this random parameter as well or is it just to enable us to use `delay: 1`? – Andre Bulatov Aug 22 '15 at 23:04
  • @andrebulatov - that data is just parameters for the ajax call on the server. In this case, it is just instructing the jsfiddle echo ajax call what data to send back. You would send whatever data is appropriate to your ajax call. – jfriend00 Aug 22 '15 at 23:08
  • Got it. Removed the 'data' as unnecessary. XHR in console now appear in correct order, however, it seems the result of the 2nd call is not having enough time to register before the 3rd call is made. Anyway I can delay it or something? Why is this happening at all, why is a deferred function going off before the previous one has 100% completed? – Andre Bulatov Aug 22 '15 at 23:12
  • 1
    @andrebulatov - you will have to show us the actual code you are now using. No delay should be needed if done properly. – jfriend00 Aug 22 '15 at 23:42
  • @AndreBulatov - What does "enough time to register before the 3rd call" mean? I'm quite sure the `.then()` handler is not firing before the ajax operation is done. It probably has to do with what you're passing to a function. We would have to see the actual code to see what is amiss. – jfriend00 Aug 23 '15 at 00:05
  • Sorry for the delay in response. Please see my update 2, I think I'm still having a problem with the 3rd AJAX call/DOM update. – Andre Bulatov Aug 23 '15 at 04:35
  • 1
    @AndreBulatov - if you have a concurrency or timing problem in your backend PHP with the 3rd request, there's not much we can do here since you haven't disclosed any of that code (and I don't know PHP either). Again, properly designed code should not require a `setTimeout()` to delay the 3rd call - much better to fix the timing issue at the source. Without seeing at least the exact front-end code that you're having the trouble with, I don't know you expect us to do. If coded properly, the sequence of `.then()` operations will not trigger the 3rd ajax call until the 2nd is complete. – jfriend00 Aug 23 '15 at 04:40
  • I understand the difficulty with working partially int he dark, but unfortunately I have absolutely 0 access to the PHP by design (as part of ecommerce platform "offering"). The code I'm using is exactly as yours in the jsfiddle -- please see my update 2 again, I've added the newest version after your suggestions. – Andre Bulatov Aug 23 '15 at 04:43
  • 1
    @AndreBulatov - though I don't recommend `setTimeout()` to fix the problem, your description of what happens when you use it makes me think you aren't using it properly. My guess is that you're calling the function directly and passing the return value to `setTimeout()` rather than passing a function reference to `setTimeout()`. See [this answer](http://stackoverflow.com/questions/23419929/calling-the-settimeout-function-recursively-and-passing-an-anonymous-function/23419951#23419951) for a description of a common mistake with `setTimeout()`. – jfriend00 Aug 23 '15 at 04:44
  • The setTimeout, as well as your previous comment about `()`, are great, thank you. I was definitely ignorantly doing this all over the place. I'm gonna try setTimeout again. – Andre Bulatov Aug 23 '15 at 04:49
  • 1
    @AndreBulatov - Did you try replacing `ExpressCheckout.ChooseShippingProvider()` with`setTimeout(function () {ExpressCheckout.ChooseShippingProvider();}, 5000);` – jfriend00 Aug 23 '15 at 04:49
  • Awesome! Just did ` return setTimeout` properly and even 10 rather than 1000 immediately makes everything load perfectly every time. And I understand what you mean that proper PHP shouldn't require this but the PHP they wrote is not only craggy and old but also not at all planned for the hijacking I'm doing (which albeit to us is still necessary). Thank you very much! – Andre Bulatov Aug 23 '15 at 04:57
1

If you "chain" AJAX post operations (meaning that you do your process once receiving the data from the previous call) then nothing strange should happen.

From the symptom I'd think more to some cache-related problem. Adding an extra random value to the query is a quick'n dirty way to get rid of whoever is caching the result and responding instead of who should. Also using a POST request instead of a GET (if possible) may help on this issue and better conveys the idea that the operation is a mutation that should not be skipped or done out of order.

Note that a stale response problem could be at several levels: browser, proxy, web server, cms plugin...

6502
  • 112,025
  • 15
  • 165
  • 265
  • The caching is definitely a possibility. I am on an ecommerce out-of-the-box platform, and it's quite possible they are caching it server-side as they do stuff like that often in other places. By random, do you mean something like `/cart.php?action=add&product_id=1280&rand=om`? – Andre Bulatov Aug 22 '15 at 22:22
  • 1
    I mean just adding another extra parameter in the url like `"rndv="+Math.random()` that is normally ignored by services but that can help getting rid of caching layers (even ones that don't respect cache control headers). – 6502 Aug 22 '15 at 22:26