0

I've been staring at this issue for a few hours. I had a boilerplate code sample I was working off as follows

$('.save_stuff').on("click", function(e) 
{
    e.preventDefault();
    e.stopPropagation();

    preprocessAddProvisioning();

    var mac = $('#c_mac_address').val();
    var make = $('#c_make').html();
    var model = $('#c_model option:selected').text();
    var regex = /[a-zA-Z0-9]{12}/g;

    if (mac.match(regex) && validateNewDevice())
    {
        $.ajax({
            url: "/SomeUrl",
            dataType: 'json',
            type: 'POST',
            async: false,
            data: 
            {
                mac: mac,
                model: model,
                make: make
            },
            success: function(data)
            {
                if (data.success)
                {
                    $('#someValue').hide();
                    $('#modalNewDevice').modal('hide');

                    if (someValue !== undefined && !someValue.snom_mac_success)
                    {
                        window.location.href = "/SomeUrl?id="+someValue.newId+"&someValue=false";
                    }
                    else
                    {
                        window.location.href = "/SomeUrl?id="+data.newId;
                    }
                }
                else
                {
                    $('#c_msg').html(data.msg);
                    $('#c_msg').closest('.form-group').addClass('has-error');
                }
            },
            error: function()
            {

            }
        });
    }
});

With the method called being;

function preprocessAddProvisioning()
{
    $('#mac_load').show('fast');
}

Can someone tell me why, async: false, stops preprocessAddProvisioning() being called? I realize it's implication when thinking about the context of the ajax request, but not of the context of the listener.

Many thanks

juju
  • 553
  • 1
  • 4
  • 21
  • 2
    Because `async:false` make the request synchronous, which as the name said stop all Javascript execution until the request is complete. – Karl-André Gagnon Dec 07 '17 at 16:29
  • 1
    BTW, [don't ever use `async:false`...ever...I'm serious](https://stackoverflow.com/questions/14220321/how-do-i-return-the-response-from-an-asynchronous-call) – Liam Dec 07 '17 at 16:29
  • 1
    @Karl-AndréGagnon Ok. So another way of asking it is like this: Why does the AJAX request fire FIRST, then the `preprocessAddProvisioning()` despite it being first in the order? – ProEvilz Dec 07 '17 at 16:30
  • @ProEvilz The AJAX request doesn't prevent `preprocessAddProvisioning()` from executing. It prevents the window from updating, so it _appears_ that `preprocessAddProvisioning()` has done nothing. – JLRishe Dec 07 '17 at 16:32
  • 1
    @ProEvilz `preprocessAddProvisioning()` is an animation. jQuery animation uses an internal timer for every steps. Timers are stopped when launching the synchronous request until it is completed. – Karl-André Gagnon Dec 07 '17 at 16:32
  • Javascript has a single thread(ish) when you force javascript to block (`async:false`) you kill this thread (and whatever it's doing) until it responds. Which is why you should never do this. The "fix" is [*Embrace the asynchronous nature of JavaScript!*](https://stackoverflow.com/a/14220323/542251) – Liam Dec 07 '17 at 16:33
  • 1
    The AJAX call is not first, the problem is that `.show` probably uses `setTimeout` or `setInterval` and those functions get scheduled after the AJAX call. – Titus Dec 07 '17 at 16:33
  • @Titus Okay, understood. – ProEvilz Dec 07 '17 at 16:38

1 Answers1

3

The AJAX request doesn't prevent preprocessAddProvisioning() from executing. It blocks the UI thread and prevents the window from updating, so it appears that preprocessAddProvisioning() has done nothing. On top of that, .show('fast') uses an animation, which would require not just one UI update, but many over a period of several milliseconds. With the JavaScript execution stopped, jQuery can't even try to carry out that animation, let alone have the window display it.

Demonstration of a window failing to update due to a blocked UI thread:

document.getElementsByTagName('input')[0].addEventListener('click', function (){
    var div = document.getElementsByTagName('div')[0];
    // hide div
    div.style.display = 'none';
    
    // block thread for 3 seconds
    var dt = new Date();
    while (new Date() - dt < 3000) {}
});
div { width: 100px; height: 100px; background-color: red; }
<div>

</div>
<input type="button" value="Click me" />

Moral of the story: don't use async: false, but if you absolutely have to, you can use a timeout to ensure that your loader has finished displaying before you make the AJAX request:

setTimeout(function () {
    makeTheAjaxRequest();
}, 500);

Bear in mind that if your loader involves an animated .gif or anything like that, the .gif will be frozen while the UI thread is blocked.

An even more precise option would be to use the complete callback on .show() to continue executing your steps once the animation is complete, but all that's likely to accomplish is spaghetti-fying your code. The real answer is to not use async: false.

JLRishe
  • 99,490
  • 19
  • 131
  • 169