37

Actualmente tengo esta función:

function devuelveButaca(posicion){
    var array = posicion.split('_');
    var row = array[0];
    var column = array[1];
    var planta = $('#plantaField').val();
    var resultado = "";
    $.ajax({
        type : 'GET',
        url : 'site/numButaca',
        data : {
            planta : planta,
            column : column,
            row : row
        },
        success : function(data) {
            if(data == 'undefined' || data == ''){
                resultado = column;

            }else{
                resultado = data;
            }
        },
        error : function(request, status, error) {

        },
    });
    alert(resultado);
    return resultado;
}

La cual devuelve el resultado correctamente, el problema es que a menos que entre en modo debug, o ponga un alert antes del return, resultado vuelve vacío. Actualmente, el alert sale vacío pero devuelve correctamente el resultado (la uso para agregar el texto de la variable a un div).

He leído que se puede solucionar poniendo un Timeout, pero me gustaría saber si hay alguna solución mas elegante que esta.

Pablo Lozano
  • 45,934
  • 7
  • 48
  • 87
Raider
  • 1,097
  • 1
  • 10
  • 25

4 Answers4

40

Explicación del problema.

La A en Ajax significa Asíncrono; esto quiere decir que la petición está fuera del flujo normal de ejecución. En tu código el $.ajax, al ejecutarse, continúa el return, return resultado, y esta es ejecutada antes de que la función o petición ajax pase el valor de la respuesta al success. Por ende cuando usas el alert te devuelve vacío o undefined, ya que no ha vuelto la petición que hiciste.

Posibles soluciones que le puedes dar a tu problema:

ES2017+: Promises with async/await

La versión de ECMAScript lanzada en 2017 introdujo el soporte para funciones asincronas. Con la ayuda de async and await, tu puedes escribir código asíncrono en un estilo síncrono. El código seguirá siendo asíncrono pero es mas facíl de leer y comprender.

Async/await esta basado en promesas, una función async siempre retornará una promesa. el Await saca la información de la promesa y el resultado será el valor que fue resuelto, o de lo contrario el error en caso de que haya sido rejectada.

Acá un ejemplo, de como crear el async en un nivel alto de función:

// Using 'superagent' which will return a promise.
var superagent = require('superagent')

// This is isn't declared as `async` because it already returns a promise
function delay() {
  // `delay` returns a promise
  return new Promise(function(resolve, reject) {
    // Only `delay` is able to resolve or reject the promise
    setTimeout(function() {
      resolve(42); // After 3 seconds, resolve the promise with value 42
    }, 3000);
  });
}

Uso de Async en funciones:

async function getAllBooks() {
  try {
    // GET a list of book IDs of the current user
    var bookIDs = await superagent.get('/user/books');
    // wait for 3 seconds (just for the sake of this example)
    await delay();
    // GET information about each book
    return await superagent.get('/books/ids='+JSON.stringify(bookIDs));
  } catch(error) {
    // If any of the awaited promises was rejected, this catch block
    // would catch the rejection reason
    return null;
  }
}

// Start an IIFE to use `await` at the top level
(async function(){
  let books = await getAllBooks();
  console.log(books);
})();

Current browser and node versions support async/await. You can also support older environments by transforming your code to ES5 with the help of regenerator (or tools that use regenerator, such as Babel).

Las actuales versiones de los navegadores y node suportan async / await. Tambien es soportdo por las versiones anteriores haciendo uso de ES5 bajo las herramientas tales como Babel.

Usar funciones callbacks: Un callback es una función que será invocada más tarde cuando la petición Ajax haya finalizado.

Ejemplo:

function hacerPeticionAjax(url, callback) { // callback es solo el nombre de la variable
    $.ajax(url, {
        // ...
        success: callback,
        // ...
    });
}

function alRecibirAnimales(animales) {
  // animales contendrá la respuesta al ajax
} 

hacerPeticionAjax('/api/animales', alRecibirAnimales);

Este ejemplo demuestra que la propiedad success debe recibir una función, que jQuery invocará al finalizar la solicitud si tuvo éxito. Por cierto, hay otro callback disponible para controlar qué hacer en caso de errores. Se maneja utilizando la propiedad error; recibe un callback que sera invocado en caso de no éxito.

Usar las promesas:

Las promesas son contenedores de valores futuros. Cuando una promesa recibe el valor si fue resuelta o cancelada, él notificará a los escuchas que quieren acceder a ese valor retornado. Uno de los valores agregados es que tiene mayor lectura de código y a mi parecer es un enfoque mucho mejor. Ejemplo:

function delay() {
  // `delay` returns a promise
  return new Promise(function(resolve, reject) {
    // Only `delay` is able to resolve or reject the promise
    setTimeout(function() {
      resolve(42); // After 3 seconds, resolve the promise with value 42
    }, 3000);
  });
}

delay().then(function(v) { // `delay` returns a promise
  console.log(v); // Log the value once it is resolved
}).catch(function(v) {
  // Or do something else if it is rejected 
  // (it would not happen in this example, since `reject` is not called).
});

Usar Jquery Deferred

Esta es una implementación customizada de las promesas implementada por jQuery; el uso es muy similar al punto explicado anteriormente:

function ajax() {
    return $.ajax(...);
}

ajax().done(function(result) {
    // Code depending on result
}).fail(function() {
    // An error occurred
});

Fuente - How do I return the response from an asynchronous call?

Wilfredo
  • 2,455
  • 3
  • 20
  • 35
  • También se pueden usar generadores o funciones asyncronas junto con las Promesas, no? – Lorthas Jul 14 '17 at 12:21
  • @Lorthas Si, lo puedes hacer. Si deseas actualizar mi respuesta colocando la explicación mas un ejemplo sería genial, yo por ahora no tengo mucho tiempo. – Wilfredo Jul 14 '17 at 15:49
9

una de las soluciones que podrías hacer es quitando el Asincrono para que tengas una respuesta inmediata colocando(async: false).

function devuelveButaca(posicion){
var array = posicion.split('_');
var row = array[0];
var column = array[1];
var planta = $('#plantaField').val();
var resultado = "";
$.ajax({
    async: false,
    type : 'GET',
    url : 'site/numButaca',
    data : {
        planta : planta,
        column : column,
        row : row
    },
    success : function(data) {
        if(data == 'undefined' || data == ''){
            resultado = column;

        }else{
            resultado = data;
        }
    },
    error : function(request, status, error) {

    },
});
alert(resultado);
return resultado;
}
Cristian Giron
  • 119
  • 1
  • 2
  • Interesante, pensé en una respuesta como ésta +1 – Máxima Alekz Mar 25 '17 at 15:28
  • Funciono para mi !! – demepty Aug 13 '18 at 23:52
  • 2
    Configurar una petición Ajax como `async = false` es una mala práctica, es contrario a la naturaleza misma de Ajax y hay que saber que cuando se hace eso, la UI quedará bloqueada mientras la petición no se haya enviado y procesada. Precisamente Ajax es asíncrono para no bloquear el hilo principal y que todo lo demás pueda seguir adelante mientras la petición se procesa. – A. Cedano Apr 13 '21 at 22:31
7

Al hacer una llamada Ajax este se ejecuta de manera asíncrona, es decir mientras se hace la petición sigue la ejecución de la función que tenes ahí.

Para eso tenes los callback de success y error.

Si pones un alert dentro del Success vas a poder ver el resultado.

$.ajax({
    type : 'GET',
    url : 'site/numButaca',
    data : {
        planta : planta,
        column : column,
        row : row
    },
    success : function(data) {
        if(data == 'undefined' || data == ''){
            resultado = column;

        }else{
            resultado = data;
        }
        alert(resultado);
    },
    error : function(request, status, error) {

    },
});

Una solución seria pasarle a tu funcion un callback para ejecutarlo.

function devuelveButaca(posicion, callback){
    var array = posicion.split('_');
    var row = array[0];
    var column = array[1];
    var planta = $('#plantaField').val();
    var resultado = "";
    $.ajax({
        type : 'GET',
        url : 'site/numButaca',
        data : {
            planta : planta,
            column : column,
            row : row
        },
        success : function(data) {
            if(data == 'undefined' || data == ''){
               resultado = column;
            }else{
               resultado = data;
            }
            if(callback)
                callback(resultado);
        },
        error : function(request, status, error) {

        },
    });    
 }

Y lo llamas de la siguiente forma:

    devuelveButaca(1, function(resultado){ alert(resultado) });
maxigs7
  • 91
  • 4
5

El valor de resultado se devuelve vacío porque se consigue a través de una llamada asíncrona (AJAX) pero estás devolviéndolo de manera síncrona (entonces el valor aún no se ha instanciado).

Como dices, una posible solución sería añadir una espera (o un timeout) para asegurarte que el valor lo vas a obtener. Pero eso puede tener problemas: ¿qué pasa si el valor de la espera no es suficiente? Entonces seguirías teniendo el mismo problema y el valor se devolvería nulo.

Realmente esto es un uso incorrecto de AJAX. La idea no es que devuelvas algo (que sería síncrono) sino que utilices los valores cuando los recibas de manera asíncrona (en el success).

Para eso podrías hacer dos cosas:

  1. Mover el código desde el que llamas a la función al success. Aunque esta opción puede no ser muy buena si se llama por diferentes motivos o desde diferentes funciones a devuelveButaca().

  2. Pasa como parámetro una función de callback que se llamará desde el success. De este modo, podrías llamar a devuelveButaca() desde diferentes contextos y siempre funcionaría.

    function devuelveButaca(posicion, callback){
        var array = posicion.split('_');
        var row = array[0];
        var column = array[1];
        var planta = $('#plantaField').val();
        var resultado = "";
        $.ajax({
            type : 'GET',
            url : 'site/numButaca',
            data : {
                planta : planta,
                column : column,
                row : row
            },
            success : function(data) {
                if(data == 'undefined' || data == ''){
                    resultado = column;
    
                }else{
                    resultado = data;
                }
    
                callback(resultado);
            },
            error : function(request, status, error) {
    
            },
        });
    }
    
Alvaro Montoro
  • 48,157
  • 26
  • 100
  • 179