281

I have a server side function that requires login. If the user is logged in the function will return 1 on success. If not, the function will return the login-page.

I want to call the function using Ajax and jQuery. What I do is submit the request with an ordinary link, with a click-function applied on it. If the user is not logged in or the function fails, I want the Ajax-call to return true, so that the href triggers.

However, when I use the following code, the function exits before the Ajax call is done.

How can I redirect the user gracefully to the loginpage?

$(".my_link").click(
    function(){
    $.ajax({
        url: $(this).attr('href'),
        type: 'GET',
        cache: false,
        timeout: 30000,
        error: function(){
            return true;
        },
        success: function(msg){ 
            if (parseFloat(msg)){
                return false;
            } else {
                return true;
            }
        }
    });
});
Peter Mortensen
  • 30,738
  • 21
  • 105
  • 131
Hobhouse
  • 15,463
  • 12
  • 35
  • 43
  • 2
    Might be an old thread, but as @kofifus points out setting async to false is a bad design and "no timeouts etc will be processed". Might be try this simplified solution - https://stackoverflow.com/a/11576418/6937841 – Shan Jun 28 '17 at 18:47
  • You can change `return true;` to `window.location.href = url;` and it would gracefully redirect to the login page once ajax call is finished. – lisandro Feb 18 '21 at 15:30

10 Answers10

418

If you don't want the $.ajax() function to return immediately, set the async option to false:

$(".my_link").click(
    function(){
    $.ajax({
        url: $(this).attr('href'),
        type: 'GET',
        async: false,
        cache: false,
        timeout: 30000,
        fail: function(){
            return true;
        },
        done: function(msg){ 
            if (parseFloat(msg)){
                return false;
            } else {
                return true;
            }
        }
    });
});

But, I would note that this would be counter to the point of AJAX. Also, you should be handling the response in the fail and done functions. Those functions will only be called when the response is received from the server.

mmv_sat
  • 458
  • 8
  • 15
cgp
  • 41,026
  • 12
  • 101
  • 131
  • 14
    Passing along good practices, in my opinion, isn't judging, and is the mark of some of the best answers here on StackOverflow. – semperos Aug 01 '11 at 14:14
  • 86
    **Never use `async: false`.** The browser's event loop will hang while waiting on unreliable network I/O. There's always a better way. In this case, the link's target can verify the user session and 302 to the login page. – Matthew Dec 14 '11 at 01:58
  • 4
    For testing, `async: false` can be very useful. – Jarrett Sep 09 '13 at 20:14
  • Where have you been all my life? – Mkl Rjv Oct 15 '14 at 15:43
  • 4
    @Matthew Really? What is the alternative to sending an ajax with async before sending user to other page or refreshing? – NoBugs Oct 15 '14 at 18:10
  • @NoBugs: always return false in click handler, and in success callback, navigate to where it should go. – Frédéric Apr 21 '15 at 11:05
  • 3
    `async` with value `false` is gone deprecated in most browser use cases. It may throw exceptions in newer version of browsers. See https://xhr.spec.whatwg.org/#sync-warning (applies to `async` parameter of xhr `open` method, which is what uses jQuery). – Frédéric Apr 21 '15 at 11:06
  • @Frederic First of all`return false` is [not recommended](http://fuelyourcoding.com/jquery-events-stop-misusing-return-false/), second, if you do preventDefault for browser click behavior, you have to handle such events for all links or code that does a page-navigation. (and waiting some amount of time while the pre-leave-page-ajax runs is basically the same problem @Matthew is talking about. If the ajax never finishes it would not go to the other page. – NoBugs Apr 23 '15 at 13:58
  • @NoBugs, yes I know about `return false` being not the best practice in a jQuery event context, but I was just sticking to what the op was practicing himself. In short, I do not think this is the point here. And yes, what the op want to do has the drawbacks you state, it is not specific to what I have suggested. – Frédéric Apr 23 '15 at 16:11
  • 2
    Note that async: false doesn't support cross-domain and jsonp requests. – Steve Sep 15 '15 at 19:05
  • 2
    @Matthew after a discussion with our tech lead I tend to agree with his recommendation to revise your statement slightly. **Almost never use `async: false`**. There are always edge cases where doing this could make sense. – w00ngy Aug 06 '18 at 15:52
  • How is this a good answer? Not using async=false makes sense, however, there doesn't seem to be an alternative answer. How DO you WAIT for an async ajax call to finish? – MC9000 Nov 15 '19 at 11:06
  • The real answer is where he says: _Also, you should be handling the response in the error and success functions._ – kr37 May 08 '20 at 15:16
  • Ah nice to know :p evergreen submit, cgp ! – Goodies Sep 12 '20 at 17:24
  • 'done' hasn't worked for me, I've used `success: function(msg,status,jqXHR)` instead and worked – lisandro Feb 18 '21 at 15:31
  • 1
    This answer was prob good 12 years ago. However is 2021 this DOES NOT WORK – Beanie Apr 08 '21 at 17:25
47

I am not using $.ajax but the $.post and $.get functions, so if I need to wait for the response, I use this:

$.ajaxSetup({async: false});
$.get("...");
fragilewindows
  • 1,394
  • 1
  • 15
  • 26
MilMike
  • 12,571
  • 15
  • 65
  • 82
38

The underlying XMLHttpRequest object (used by jQuery to make the request) supports the asynchronous property. Set it to false. Like

async: false
idrosid
  • 7,983
  • 5
  • 44
  • 41
29

Instead of setting async to false which is usually bad design, you may want to consider blocking the UI while the operation is pending.

This can be nicely achieved with jQuery promises as follows:

// same as $.ajax but settings can have a maskUI property
// if settings.maskUI==true, the UI will be blocked while ajax in progress
// if settings.maskUI is other than true, it's value will be used as the color value while bloking (i.e settings.maskUI='rgba(176,176,176,0.7)'
// in addition an hourglass is displayed while ajax in progress
function ajaxMaskUI(settings) {
    function maskPageOn(color) { // color can be ie. 'rgba(176,176,176,0.7)' or 'transparent'
        var div = $('#maskPageDiv');
        if (div.length === 0) {
            $(document.body).append('<div id="maskPageDiv" style="position:fixed;width:100%;height:100%;left:0;top:0;display:none"></div>'); // create it
            div = $('#maskPageDiv');
        }
        if (div.length !== 0) {
            div[0].style.zIndex = 2147483647;
            div[0].style.backgroundColor=color;
            div[0].style.display = 'inline';
        }
    }
    function maskPageOff() {
        var div = $('#maskPageDiv');
        if (div.length !== 0) {
            div[0].style.display = 'none';
            div[0].style.zIndex = 'auto';
        }
    }
    function hourglassOn() {
        if ($('style:contains("html.hourGlass")').length < 1) $('<style>').text('html.hourGlass, html.hourGlass * { cursor: wait !important; }').appendTo('head');
        $('html').addClass('hourGlass');
    }
    function hourglassOff() {
        $('html').removeClass('hourGlass');
    }

    if (settings.maskUI===true) settings.maskUI='transparent';

    if (!!settings.maskUI) {
        maskPageOn(settings.maskUI);
        hourglassOn();
    }

    var dfd = new $.Deferred();
    $.ajax(settings)
        .fail(function(jqXHR, textStatus, errorThrown) {
            if (!!settings.maskUI) {
                maskPageOff();
                hourglassOff();
            }
            dfd.reject(jqXHR, textStatus, errorThrown);
        }).done(function(data, textStatus, jqXHR) {
            if (!!settings.maskUI) {
                maskPageOff();
                hourglassOff();
            }
            dfd.resolve(data, textStatus, jqXHR);
        });

    return dfd.promise();
}

with this you can now do:

ajaxMaskUI({
    url: url,
    maskUI: true // or try for example 'rgba(176,176,176,0.7)'
}).fail(function (jqXHR, textStatus, errorThrown) {
    console.log('error ' + textStatus);
}).done(function (data, textStatus, jqXHR) {
    console.log('success ' + JSON.stringify(data));
});

And the UI will block until the ajax command returns

see jsfiddle

doydoy44
  • 5,720
  • 4
  • 29
  • 45
kofifus
  • 17,260
  • 17
  • 99
  • 173
  • Doesn't setting async to false do the exact same thing except in a single line of code? – Vincent Dec 19 '15 at 22:51
  • 4
    Not at all. Setting async to false make the request async - that is halts processing until it returns which is usually bad practice, for example no events, other ajax requests, timeouts etc will be processed. You can also modify the code above to block only part of the UI while your ajax is processing (ie the part it will affect) – kofifus Dec 27 '15 at 06:49
13

In modern JS you can simply use async/await, like:

  async function upload() {
    return new Promise((resolve, reject) => {
        $.ajax({
            url: $(this).attr('href'),
            type: 'GET',
            timeout: 30000,
            success: (response) => {
                resolve(response);
            },
            error: (response) => {
                reject(response);
            }
        })
    })
}

Then call it in an async function like:

let response = await upload();
Taohidul Islam
  • 5,246
  • 3
  • 26
  • 39
13

I think things would be easier if you code your success function to load the appropriate page instead of returning true or false.

For example instead of returning true you could do:

window.location="appropriate page";

That way when the success function is called the page gets redirected.

Anthony Grist
  • 38,173
  • 8
  • 62
  • 76
samuelagm
  • 131
  • 1
  • 4
7

Since I don't see it mentioned here I thought I'd also point out that the jQuery when statement can be very useful for this purpose.

Their example looks like this:

$.when( $.ajax( "test.aspx" ) ).then(function( data, textStatus, jqXHR ) {
  alert( jqXHR.status ); // Alerts 200
});

The "then" part won't execute until the "when" part finishes.

Frank Hoffman
  • 887
  • 1
  • 11
  • 16
  • Can I also nest these multiple times? _(I am glad you added this option; I could not wrap my head around any of the other suggestions. This finally made sense.)_ – MeSo2 Mar 01 '23 at 01:48
5

since async ajax is deprecated try using nested async functions with a Promise. There may be syntax errors.


async function fetch_my_data(_url, _dat) {

   async function promised_fetch(_url, _dat) {

      return new Promise((resolve, reject) => {
         $.ajax({
            url:  _url,
            data: _dat,
            type: 'POST',
            success: (response) => {
               resolve(JSON.parse(response));
            },
            error: (response) => {
               reject(response);
            }
         });
      });
   }

   var _data = await promised_fetch(_url, _dat);
   
   return _data;
}

var _my_data = fetch_my_data('server.php', 'get_my_data=1');

user7793758
  • 79
  • 1
  • 4
2

It should wait until get request completed. After that I'll return get request body from where function is called.

function foo() {
    var jqXHR = $.ajax({
        url: url,
        type: 'GET',
        async: false,
    });
    return JSON.parse(jqXHR.responseText);  
}
Hassan Ejaz
  • 183
  • 2
  • 16
0

The original question was at today's date asked 12 years ago and was 'How do I make jQuery wait for an Ajax call to finish before it returns?' jQuery has come a long way since then.

There are a few solutions mentioned above and I couldn't get any of them to work with the latest version of jQuery: $.when().then.() doesn't seem to be synchronous unless its uses 'async: false' which is no longer supported, so doesn't work in newer versions of jQuery.

But promises are built into jQuery ajax calls so it shouldn't be that difficult to make ajax calls synchronous.

I use namespaced js functions so the example below is in that format. The example is for custom form validation that calls the server to validate that user input does not attempt to duplicate an existing item.

This code will probably not work in IE or Legacy Edge unless using Babel, but I tend to block those browsers as they are no longer supported by Microsoft.

///Namespace validate
check: async function(settings){
    let IsValid = false;
    let Message = ''
    let data = await validate.serverCheck('function', value);
    IsValid = data.OK;
    Message = data.Message;
}

serverCheck: async function (fn, value) {
    var request = {
        validateValue: $.sanitize(value)
    };

    let result;

    try {
            result = await $.ajax({
                dataType: "json",
                type: "post",
                url: "/api/validate/" + fn + "/",
                data: request
        });

        return result;
    } catch (x) {}
}

and there you have it

Stu
  • 71
  • 3