¿Cómo hago que jQuery espere a que finalice una llamada Ajax antes de que regrese?

243

Tengo una función del lado del servidor que requiere inicio de sesión. Si el usuario ha iniciado sesión, la función devolverá 1 en caso de éxito. Si no, la función devolverá la página de inicio de sesión.

Quiero llamar a la función usando Ajax y jQuery. Lo que hago es enviar la solicitud con un enlace ordinario, con una función de clic aplicada. Si el usuario no ha iniciado sesión o la función falla, quiero que la llamada Ajax devuelva verdadero, de modo que href se active.

Sin embargo, cuando uso el siguiente código, la función se cierra antes de que se realice la llamada Ajax.

¿Cómo puedo redirigir al usuario con gracia a la página de inicio de sesión?

$(".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;
            }
        }
    });
});
Hobhouse
fuente
2
Puede ser un hilo viejo, pero como @kofifus señala que establecer asíncrono en falso es un mal diseño y "no se procesarán tiempos de espera, etc.". Podría probar esta solución simplificada - stackoverflow.com/a/11576418/6937841
Shan

Respuestas:

355

Si no desea que la $.ajax()función regrese de inmediato, configure la asyncopción para false:

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

Pero, me gustaría señalar que esto sería contrario al punto de AJAX. Además, debe manejar la respuesta en las funciones errory success. Esas funciones solo se llamarán cuando se reciba la respuesta del servidor.

cgp
fuente
10
Transmitir buenas prácticas, en mi opinión, no es un juicio, y es la marca de algunas de las mejores respuestas aquí en StackOverflow.
semperos
68
Nunca lo use async: false. El bucle de eventos del navegador se bloqueará mientras espera E / S de red poco confiables. Siempre hay una mejor manera. En este caso, el objetivo del enlace puede verificar la sesión del usuario y 302 a la página de inicio de sesión.
Matthew
3
Para probar, async: falsepuede ser muy útil.
Jarrett
44
@Matthew ¿En serio? ¿Cuál es la alternativa a enviar un ajax con asíncrono antes de enviar al usuario a otra página o actualizarlo?
NoBugs
2
asynccon valor falseha quedado obsoleto en la mayoría de los casos de uso del navegador. Puede arrojar excepciones en la versión más nueva de los navegadores. Consulte xhr.spec.whatwg.org/#sync-warning (se aplica al asyncparámetro del openmétodo xhr , que es lo que usa jQuery).
Frédéric
42

No estoy usando $.ajaxlas funciones $.posty $.get, por lo que si necesito esperar la respuesta, uso esto:

$.ajaxSetup({async: false});
$.get("...");
MilMike
fuente
34

El objeto XMLHttpRequest subyacente (utilizado por jQuery para realizar la solicitud) admite la propiedad asincrónica. Ponlo en falso . Me gusta

async: false
idrosid
fuente
24

En lugar de establecer asíncrono en falso, que generalmente es un mal diseño, es posible que desee considerar bloquear la interfaz de usuario mientras la operación está pendiente.

Esto se puede lograr muy bien con las promesas jQuery de la siguiente manera:

// 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();
}

con esto ahora puedes hacer:

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));
});

Y la interfaz de usuario se bloqueará hasta que regrese el comando ajax

ver jsfiddle

kofifus
fuente
¿Configurar async en false no hace exactamente lo mismo excepto en una sola línea de código?
Vincent
44
De ningún modo. Establecer asíncrono en falso hace que la solicitud sea asíncrona, es decir, detiene el procesamiento hasta que regrese, lo que generalmente es una mala práctica, por ejemplo, no se procesarán eventos, otras solicitudes ajax, tiempos de espera, etc. También puede modificar el código anterior para bloquear solo una parte de la interfaz de usuario mientras su ajax está procesando (es decir, la parte que afectará)
kofifus
11

Creo que las cosas serían más fáciles si codificas tu successfunción para cargar la página apropiada en lugar de regresar trueo false.

Por ejemplo, en lugar de regresar true, podría hacer:

window.location="appropriate page";

De esa manera, cuando se llama a la función de éxito, la página se redirige.

samuelagm
fuente
5

En JS moderno, simplemente puede usar async/ await, como:

  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);
            }
        })
    })
}

Luego llámalo en una asyncfunción como:

let response = await upload();
Taohidul Islam
fuente
1
Su código no funcionará ... está en el camino correcto, pero tal como está, no funcionará.
ppetree
¿Puedes por favor elaborar más? Después de probar el código, he respondido esta pregunta, debería funcionar.
Taohidul Islam
await no se puede usar fuera de la función asincrónica, arroja un error.
ppetree
Sí, lo he mencionado en la segunda última línea de mi respuesta, "Entonces llámalo en una asyncfunción".
Taohidul Islam
Es una gran solución, pero para ayudar a otros que buscan una gran solución (como yo), debe editar su código y mostrarlo de una manera más completa. Quizás incluso incluya un enlace a un violín. ¡Que sería increíble!
ppetree
0

Como no lo veo mencionado aquí, pensé que también señalaría que la declaración jQuery when puede ser muy útil para este propósito.

Su ejemplo se ve así:

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

La parte "entonces" no se ejecutará hasta que finalice la parte "cuándo".

Frank Hoffman
fuente
0

Debe esperar hasta que se complete la solicitud. Después de eso, devolveré el cuerpo de solicitud de donde se llama a la función.

function foo() {
    var jqXHR = $.ajax({
        url: url,
        type: 'GET',
        async: false,
    });
    return JSON.parse(jqXHR.responseText);  
}
Hassan Ejaz
fuente
Por favor explique su solución.
Abandono
1
Se agregó @Dropout.
Hassan Ejaz
Gracias @Hassan Ejaz! Las respuestas que no tienen una explicación y son solo código se marcan como de bajo esfuerzo.
Abandono el