¿Cómo devuelvo la respuesta de una llamada asincrónica

javascript jquery ajax asynchronous


Tengo una función foo que hace una solicitud de Ajax. ¿Cómo puedo devolver la respuesta de foo ?

Intenté devolver el valor de la devolución de llamada success , así como asignar la respuesta a una variable local dentro de la función y devolver esa, pero ninguna de esas formas realmente devuelve la respuesta.

function foo() {
    var result;

    $.ajax({
        url: '...',
        success: function(response) {
            result = response;
            // return response; // <- I tried that one as well
        }
    });

    return result;
}

var result = foo(); // It always ends up being `undefined`.




Answer 1 Felix Kling


→ Para obtener una explicación más general del comportamiento asíncrono con diferentes ejemplos, consulte ¿Por qué no se altera mi variable después de modificarla dentro de una función? - Referencia de código asíncrono

→ Si ya comprende el problema, pase a las posibles soluciones a continuación.

El problema

La A en Ajax significa asíncrono . Eso significa que enviar la solicitud (o más bien recibir la respuesta) se elimina del flujo de ejecución normal. En su ejemplo, $.ajax regresa inmediatamente y la siguiente declaración, return result; , se ejecuta antes de que se llamara a la función que pasó como devolución de llamada de success .

He aquí una analogía que esperamos que haga más clara la diferencia entre el flujo síncrono y el asíncrono:

Synchronous

Imagina que llamas a un amigo y le pides que te busque algo.Aunque puede llevar un tiempo,esperas en el teléfono y miras fijamente al espacio,hasta que tu amigo te da la respuesta que necesitabas.

Lo mismo ocurre cuando se hace una llamada de función que contiene un código "normal":

function findItem() {
    var item;
    while(item_not_found) {
        // search
    }
    return item;
}

var item = findItem();

// Do something with item
doSomethingElse();

Aunque findItem puede tardar mucho tiempo en ejecutarse, cualquier código que venga después de var item = findItem(); tiene que esperar hasta que la función devuelva el resultado.

Asynchronous

Llamas a tu amigo nuevamente por la misma razón. Pero esta vez le dices que tienes prisa y que él debería volver a llamarte a tu teléfono móvil. Cuelgas, sales de casa y haces lo que planeaste hacer. Una vez que su amigo le devuelve la llamada, está lidiando con la información que le dio.

Eso es exactamente lo que pasa cuando haces una petición de Ajax.

findItem(function(item) {
    // Do something with item
});
doSomethingElse();

En lugar de esperar la respuesta, la ejecución continúa inmediatamente y la declaración después de que se ejecuta la llamada Ajax. Para obtener la respuesta eventualmente, usted proporciona una función a la que se llamará una vez que se recibió la respuesta, una devolución de llamada (¿nota algo? ¿ Devolver llamada ?). Cualquier declaración posterior a esa llamada se ejecuta antes de que se llame la devolución de llamada.


Solution(s)

¡Abrace la naturaleza asincrónica de JavaScript! Si bien ciertas operaciones asincrónicas proporcionan contrapartidas sincrónicas (también lo hace "Ajax"), generalmente se desaconseja usarlas, especialmente en un contexto de navegador.

¿Por qué es malo lo que preguntas?

El JavaScript se ejecuta en el hilo de la UI del navegador y cualquier proceso de larga duración bloqueará la UI,haciéndola insensible.Además,hay un límite superior en el tiempo de ejecución de JavaScript y el navegador preguntará al usuario si debe continuar la ejecución o no.

Todo esto es realmente una mala experiencia para el usuario.El usuario no podrá saber si todo funciona bien o no.Además,el efecto será peor para los usuarios con una conexión lenta.

A continuación veremos tres soluciones diferentes que se construyen una encima de la otra:

  • Promesas con async/await await (ES2017 +, disponible en navegadores antiguos si usa un transpilador o regenerador)
  • Callbacks (popular en nodo)
  • Promesas con then() (ES2015 +, disponible en navegadores antiguos si usa una de las muchas bibliotecas de promesas)

Los tres están disponibles en los navegadores actuales y en el nodo 7+.


ES2017 +: Promesas con async/await

La versión ECMAScript lanzada en 2017 introdujo soporte de nivel de sintaxis para funciones asincrónicas. Con la ayuda de async y await , puede escribir asincrónico en un "estilo sincrónico". El código sigue siendo asíncrono, pero es más fácil de leer / comprender.

async/await se basa en promesas: una función async siempre devuelve una promesa. await "desenvuelve" una promesa y resulta en el valor con el que se resolvió la promesa o arroja un error si la promesa fue rechazada.

Importante: Solo puede usar await dentro de una función async . En este momento, la await nivel superior aún no es compatible, por lo que es posible que deba realizar una IIFE asincrónica ( expresión de función invocada inmediatamente ) para iniciar un contexto async .

Puede leer más sobre async y await en MDN.

He aquí un ejemplo que se suma a la demora anterior:

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


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

Las versiones actuales de navegador y nodo admiten async/await . También puede admitir entornos más antiguos transformando su código a ES5 con la ayuda de regenerator (o herramientas que usan regenerator, como Babel ).


Dejar que las funciones acepten devoluciones de llamada

Una llamada es simplemente una función que se pasa a otra función.Esa otra función puede llamar a la función pasada cuando esté lista.En el contexto de un proceso asíncrono,se llamará a la llamada de retorno cuando el proceso asíncrono esté listo.Normalmente,el resultado se pasa a la llamada de retorno.

En el ejemplo de la pregunta, puede hacer que acepte una devolución de llamada y usarla como devolución de llamada success . Así que esto

var result = foo();
// Code that depends on 'result'

becomes

foo(function(result) {
    // Code that depends on 'result'
});

Aquí definimos la función "en línea" pero se puede pasar cualquier referencia de función:

function myCallback(result) {
    // Code that depends on 'result'
}

foo(myCallback);

foo se define de la siguiente manera:

function foo(callback) {
    $.ajax({
        // ...
        success: callback
    });
}

callback se referirá a la función que pasamos a foo cuando la llamamos y simplemente la pasamos al success . Es decir, una vez que la solicitud de Ajax es exitosa, $.ajax llamará a la callback y pasará la respuesta a la devolución de llamada (que puede ser referida con el result ado , ya que así es como definimos la devolución de llamada).

También puedes procesar la respuesta antes de pasarla a la devolución de la llamada:

function foo(callback) {
    $.ajax({
        // ...
        success: function(response) {
            // For example, filter the response
            callback(filtered_response);
        }
    });
}

Es más fácil escribir un código usando devoluciones de llamada de lo que parece.Después de todo,JavaScript en el navegador es fuertemente impulsado por eventos (eventos DOM).Recibir la respuesta de Ajax no es más que un evento.
Pueden surgir dificultades cuando se tiene que trabajar con código de terceros,pero la mayoría de los problemas se pueden resolver con sólo pensar en el flujo de la aplicación.


ES2015 +: Promesas con then ()

Promise API es una nueva característica de ECMAScript 6 (ES2015), pero ya tiene un buen soporte de navegador . También hay muchas bibliotecas que implementan la API Promises estándar y proporcionan métodos adicionales para facilitar el uso y la composición de funciones asincrónicas (por ejemplo, bluebird ).

Las promesas son contenedores para valores futuros . Cuando la promesa recibe el valor (se resuelve ) o cuando se cancela ( rechaza ), notifica a todos sus "oyentes" que desean acceder a este valor.

La ventaja sobre las llamadas simples es que permiten desacoplar el código y son más fáciles de componer.

Aquí hay un ejemplo simple de cómo usar una promesa:

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

Aplicado a nuestra llamada de Ajax podríamos usar promesas como esta:

function ajax(url) {
  return new Promise(function(resolve, reject) {
    var xhr = new XMLHttpRequest();
    xhr.onload = function() {
      resolve(this.responseText);
    };
    xhr.onerror = reject;
    xhr.open('GET', url);
    xhr.send();
  });
}

ajax("/echo/json")
  .then(function(result) {
    // Code depending on result
  })
  .catch(function() {
    // An error occurred
  });

Describir todas las ventajas que ofrecen las promesas está fuera del alcance de esta respuesta,pero si escribes un nuevo código,deberías considerarlas seriamente.Proporcionan una gran abstracción y separación de tu código.

Más información sobre promesas: HTML5 HTML5 - JavaScript Promises

Nota al margen:los objetos diferidos de jQuery

Los objetos diferidos son la implementación personalizada de promesas de jQuery (antes de que la API Promise se estandarizara). Se comportan casi como promesas pero exponen una API ligeramente diferente.

Cada método de Ajax de jQuery ya devuelve un "objeto diferido" (en realidad,una promesa de un objeto diferido)que puedes simplemente devolver de tu función:

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

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

Nota al margen:La promesa se consiguió

Tenga en cuenta que las promesas y los objetos diferidos son solo contenedores para un valor futuro, no son el valor en sí. Por ejemplo, suponga que tiene lo siguiente:

function checkPassword() {
    return $.ajax({
        url: '/password',
        data: {
            username: $('#username').val(),
            password: $('#password').val()
        },
        type: 'POST',
        dataType: 'json'
    });
}

if (checkPassword()) {
    // Tell the user they're logged in
}

Este código no comprende los problemas de asincronía anteriores. Específicamente, $.ajax() no congela el código mientras verifica la página '/ contraseña' en su servidor: envía una solicitud al servidor y mientras espera, inmediatamente devuelve un objeto jQuery Ajax diferido, no la respuesta del servidor Eso significa que la instrucción if siempre obtendrá este objeto diferido, lo tratará como true y procederá como si el usuario hubiera iniciado sesión. No es bueno.

Pero el arreglo es fácil:

checkPassword()
.done(function(r) {
    if (r) {
        // Tell the user they're logged in
    } else {
        // Tell the user their password was bad
    }
})
.fail(function(x) {
    // Tell the user something bad happened
});

No se recomienda:Llamadas sincronizadas de "Ajax".

Como mencioné,algunas operaciones asíncronas tienen contrapartes síncronas.No abogo por su uso,pero para completar,así es como se realizaría una llamada síncrona:

Sin jQuery

Si usa directamente un objeto XMLHTTPRequest , pase false como tercer argumento a .open .

jQuery

Si usa jQuery , puede establecer la opción async en false . Tenga en cuenta que esta opción está en desuso desde jQuery 1.8. A continuación, puede seguir utilizando una devolución de llamada correcta o acceder a la propiedad responseText del objeto jqXHR :

function foo() {
    var jqXHR = $.ajax({
        //...
        async: false
    });
    return jqXHR.responseText;
}

Si usa cualquier otro método jQuery Ajax, como $.get , $.getJSON , etc., debe cambiarlo a $.ajax (ya que solo puede pasar los parámetros de configuración a $.ajax ).

¡Aviso! No es posible realizar una solicitud JSONP sincrónica. JSONP, por su propia naturaleza, siempre es asíncrono (una razón más para no considerar esta opción).




Answer 2 Benjamin Gruenbaum


Si no está utilizando jQuery en su código, esta respuesta es para usted

Tu código debería ser algo parecido a esto:

function foo() {
    var httpRequest = new XMLHttpRequest();
    httpRequest.open('GET', "/echo/json");
    httpRequest.send();
    return httpRequest.responseText;
}

var result = foo(); // always ends up being 'undefined'

Felix Kling hizo un buen trabajo escribiendo una respuesta para la gente que usa jQuery para AJAX.

( Nota, para aquellos que usan la nueva API de fetch , Angular o promesas, he agregado otra respuesta a continuación )


A lo que te enfrentas

Este es un breve resumen de la "Explicación del problema" de la otra respuesta,si no estás seguro después de leer esto,lee eso.

La A en AJAX significa asíncrono . Eso significa que enviar la solicitud (o más bien recibir la respuesta) se elimina del flujo de ejecución normal. En su ejemplo, .send devuelve inmediatamente y la siguiente instrucción, return result; , se ejecuta antes de que se llamara a la función que pasó como devolución de llamada de success .

Esto significa que cuando estás regresando,el oyente que has definido no se ha ejecutado todavía,lo que significa que el valor que estás regresando no ha sido definido.

Aquí hay una simple analogía

function getFive(){ 
    var a;
    setTimeout(function(){
         a=5;
    },10);
    return a;
}

(Fiddle)

El valor de a devuelto undefined está definido ya que la parte a=5 aún no se ha ejecutado. AJAX actúa así, está devolviendo el valor antes de que el servidor tenga la oportunidad de decirle a su navegador cuál es ese valor.

Una posible solución a este problema es codificar de forma reactiva , diciéndole a su programa qué hacer cuando se complete el cálculo.

function onComplete(a){ // When the code completes, do this
    alert(a);
}

function getFive(whenDone){ 
    var a;
    setTimeout(function(){
         a=5;
         whenDone(a);
    },10);
}

Esto se llama CPS . Básicamente, le estamos pasando a getFive una acción para que se realice cuando se complete, le estamos diciendo a nuestro código cómo reaccionar cuando se completa un evento (como nuestra llamada AJAX, o en este caso el tiempo de espera).

El uso sería:

getFive(onComplete);

Que debería alertar "5" a la pantalla. (Violín)

Posibles soluciones

Hay básicamente dos maneras de resolver esto:

  1. Haz la llamada AJAX sincronizada (llamémosla SJAX).
  2. Reestructura tu código para que funcione correctamente con las devoluciones de llamada.

1.AJAX síncrono-¡¡No lo hagas!!

En cuanto a AJAX síncrono, ¡no lo hagas! La respuesta de Felix plantea algunos argumentos convincentes sobre por qué es una mala idea. Para resumir, congelará el navegador del usuario hasta que el servidor devuelva la respuesta y cree una experiencia de usuario muy mala. Aquí hay otro breve resumen tomado de MDN sobre por qué:

XMLHttpRequest soporta tanto comunicaciones sincrónicas como asincrónicas.Sin embargo,en general,las solicitudes asincrónicas deben preferirse a las síncronas por razones de rendimiento.

En resumen,las peticiones síncronas bloquean la ejecución del código......esto puede causar serios problemas...

Si tiene que hacerlo, puede pasar una bandera: así es como:

var request = new XMLHttpRequest();
request.open('GET', 'yourURL', false);  // `false` makes the request synchronous
request.send(null);

if (request.status === 200) {// That's HTTP for 'ok'
  console.log(request.responseText);
}

2.Reestructurar el código

Deje que su función acepte una devolución de llamada. En el código de ejemplo, se puede hacer que foo acepte una devolución de llamada. Le diremos a nuestro código cómo reaccionar cuando se complete foo .

So:

var result = foo();
// code that depends on `result` goes here

Becomes:

foo(function(result) {
    // code that depends on `result`
});

Aquí pasamos una función anónima,pero también podríamos pasar una referencia a una función existente,haciendo que parezca:

function myHandler(result) {
    // code that depends on `result`
}
foo(myHandler);

Para más detalles sobre cómo se hace este tipo de diseño de devolución de llamada,comprueba la respuesta de Félix.

Ahora,vamos a definir el propio Foo para actuar en consecuencia

function foo(callback) {
    var httpRequest = new XMLHttpRequest();
    httpRequest.onload = function(){ // when the request is loaded
       callback(httpRequest.responseText);// we're calling our method
    };
    httpRequest.open('GET', "/echo/json");
    httpRequest.send();
}

(fiddle)

Ahora hemos hecho que nuestra función foo acepte una acción para ejecutar cuando el AJAX se complete con éxito,podemos ampliar esto aún más comprobando si el estado de respuesta no es 200 y actuando en consecuencia (crear un manejador de fallos y tal).Resolviendo efectivamente nuestro problema.

Si todavía tiene dificultades para entender esto, lea la guía de inicio de AJAX en MDN.




Answer 3 cocco


XMLHttpRequest 2 (en primer lugar, lea las respuestas de Benjamin Gruenbaum y Felix Kling )

Si no usas jQuery y quieres un bonito y corto XMLHttpRequest 2 que funciona en los navegadores modernos y también en los navegadores móviles te sugiero que lo uses de esta manera:

function ajax(a, b, c){ // URL, callback, just a placeholder
  c = new XMLHttpRequest;
  c.open('GET', a);
  c.onload = b;
  c.send()
}

Como puede ver:

  1. Es más corto que todas las demás funciones de la lista.
  2. La devolución de llamada se fija directamente (así que no hay cierres adicionales innecesarios).
  3. Utiliza la nueva carga (para que no tenga que verificar el estado listo &&)
  4. Hay otras situaciones que no recuerdo que hacen que el XMLHttpRequest 1 sea molesto.

Hay dos maneras de obtener la respuesta de esta llamada de Ajax (tres usando el nombre de var XMLHttpRequest):

El más simple:

this.response

O si por alguna razón bind() la devolución de llamada a una clase:

e.target.response

Example:

function callback(e){
  console.log(this.response);
}
ajax('URL', callback);

O (lo anterior es mejor las funciones anónimas son siempre un problema):

ajax('URL', function(e){console.log(this.response)});

Nada más fácil.

Ahora algunas personas probablemente dirán que es mejor usar onreadystatechange o incluso el nombre de la variable XMLHttpRequest.Eso está mal.

Consulte las funciones avanzadas de XMLHttpRequest

Soportaba todos los *navegadores modernos.Y puedo confirmar que estoy usando este enfoque desde que existe XMLHttpRequest 2.Nunca he tenido ningún tipo de problema en todos los navegadores que uso.

El cambio de estado sólo es útil si quieres obtener los encabezados en el estado 2.

El uso del nombre de variable XMLHttpRequest es otro gran error, ya que debe ejecutar la devolución de llamada dentro de los cierres onload / oreadystatechange, de lo contrario lo perderá.


Ahora si quieres algo más complejo usando post y FormData puedes extender fácilmente esta función:

function x(a, b, e, d, c){ // URL, callback, method, formdata or {key:val},placeholder
  c = new XMLHttpRequest;
  c.open(e||'get', a);
  c.onload = b;
  c.send(d||null)
}

Una vez más ... es una función muy corta, pero obtiene y publica.

Ejemplos de uso:

x(url, callback); // By default it's get so no need to set
x(url, callback, 'post', {'key': 'val'}); // No need to set post data

O pase un elemento de formulario completo ( document.getElementsByTagName('form')[0] ):

var fd = new FormData(form);
x(url, callback, 'post', fd);

O establecer algunos valores personalizados:

var fd = new FormData();
fd.append('key', 'val')
x(url, callback, 'post', fd);

Como puedes ver,no implementé la sincronización...es algo malo.

Dicho esto...¿por qué no lo haces de la manera más fácil?


Como se mencionó en el comentario, el uso de error && synchronous rompe completamente el punto de la respuesta. ¿Cuál es una buena forma de usar Ajax de la manera adecuada?

Controlador de errores

function x(a, b, e, d, c){ // URL, callback, method, formdata or {key:val}, placeholder
  c = new XMLHttpRequest;
  c.open(e||'get', a);
  c.onload = b;
  c.onerror = error;
  c.send(d||null)
}

function error(e){
  console.log('--Error--', this.type);
  console.log('this: ', this);
  console.log('Event: ', e)
}
function displayAjax(e){
  console.log(e, this);
}
x('WRONGURL', displayAjax);

En el guión anterior,tienes un manejador de errores que está definido estáticamente para que no comprometa la función.El gestor de errores puede utilizarse también para otras funciones.

Pero para sacar realmente un error, la única forma es escribir una URL incorrecta, en cuyo caso cada navegador arroja un error.

Los manejadores de errores son útiles si se establecen cabeceras personalizadas,si se establece el responseType en el buffer de la matriz de blob o lo que sea...

Incluso si pasas "POSTAPAPAP" como el método no arrojará un error.

Incluso si pasas 'fdggdgilfdghfldj' como formdata,no te dará un error.

En el primer caso, el error está dentro de displayAjax() en this.statusText como Method not Allowed .

En el segundo caso,simplemente funciona.Tienes que comprobar en el lado del servidor si has pasado los datos del puesto correcto.

El dominio cruzado no permitido arroja un error automáticamente.

En la respuesta de error,no hay códigos de error.

Solo existe this.type , que está configurado como error.

¿Por qué agregar un controlador de errores si no tiene control sobre los errores? La mayoría de los errores se devuelven dentro de esto en la función de devolución de llamada displayAjax() .

Así que..:No hay necesidad de comprobar los errores si eres capaz de copiar y pegar la URL correctamente.;)

PD: Como la primera prueba, escribí x ('x', displayAjax) ..., y obtuve una respuesta ... ¿??? Así que verifiqué la carpeta donde se encuentra el HTML y había un archivo llamado 'x.xml'. Entonces, incluso si olvida la extensión de su archivo, XMLHttpRequest 2 LO ENCONTRARÁ . Yo jajaja


Leer un archivo sincrónico

No hagas eso.

Si desea bloquear el navegador por un tiempo, cargue un gran archivo .txt sincrónico.

function omg(a, c){ // URL
  c = new XMLHttpRequest;
  c.open('GET', a, true);
  c.send();
  return c; // Or c.response
}

Ahora puedes hacer

 var res = omg('thisIsGonnaBlockThePage.txt');

No hay otra forma de hacer esto de forma no asincrónica.(Sí,con el bucle setTimeout...pero ¿en serio?)

Otro punto es...si trabajas con APIs o sólo con los archivos de tu propia lista o lo que sea,siempre usas diferentes funciones para cada solicitud...

Sólo si tienes una página en la que cargas siempre el mismo XMLJSON o lo que sea,sólo necesitas una función.En ese caso,modifica un poco la función de Ajax y reemplaza b con tu función especial.


Las funciones anteriores son de uso básico.

Si quiere EXTENDER la función...

Sí,puede.

Estoy usando un montón de APIs y una de las primeras funciones que integro en cada página HTML es la primera función Ajax en esta respuesta,con GET solamente...

Pero puedes hacer muchas cosas con XMLHttpRequest 2:

Hice un gestor de descargas (usando rangos en ambos lados con curriculum vitae,filereader,sistema de archivos),varios convertidores de redimensionamiento de imágenes usando lienzo,poblar bases de datos web SQL con imágenes base64 y mucho más...Pero en estos casos debes crear una función sólo para ese propósito...a veces necesitas un blob,buffers de matriz,puedes establecer encabezados,anular mimetipos y hay mucho más...

Pero la pregunta aquí es cómo devolver una respuesta de Ajax...(Añadí una manera fácil.)




Answer 4 Benjamin Gruenbaum


Si estás usando promesas,esta respuesta es para ti.

Esto significa AngularJS,jQuery (con diferido),reemplazo de XHR nativo (fetch),EmberJS,BackboneJS's save o cualquier biblioteca de nodos que devuelva promesas.

Tu código debería ser algo parecido a esto:

function foo() {
    var data;
    // or $.get(...).then, or request(...).then, or query(...).then
    fetch("/echo/json").then(function(response){
        data = response.json();
    });
    return data;
}

var result = foo(); // result is always undefined no matter what.

Felix Kling hizo un buen trabajo escribiendo una respuesta para la gente que usa jQuery con llamadas para AJAX.Tengo una respuesta para XHR nativo.Esta respuesta es para el uso genérico de promesas ya sea en el frontend o en el backend.


La cuestión central

El modelo de concurrencia de JavaScript en el navegador y en el servidor con NodeJS / io.js es asíncrono y reactivo .

Cada vez que llama a un método que devuelve una promesa, los controladores de then siempre se ejecutan de forma asíncrona, es decir, después del código debajo de ellos que no está en un controlador .then .

Esto significa que cuando devuelve data el controlador que definió aún no se ejecutó. Esto a su vez significa que el valor que está devolviendo no se ha establecido en el valor correcto a tiempo.

Aquí hay una simple analogía para el tema:

    function getFive(){
        var data;
        setTimeout(function(){ // set a timer for one second in the future
           data = 5; // after a second, do this
        }, 1000);
        return data;
    }
    document.body.innerHTML = getFive(); // `undefined` here and not 5

El valor de los data undefined está definido ya que la parte data = 5 aún no se ha ejecutado. Probablemente se ejecutará en un segundo, pero para ese momento ya no es relevante para el valor devuelto.

Como la operación aún no se ha realizado (AJAX,llamada a un servidor,IO,temporizador)estás devolviendo el valor antes de que la petición tenga la oportunidad de decir a tu código cuál es ese valor.

Una posible solución a este problema es codificar de forma reactiva , diciéndole a su programa qué hacer cuando se complete el cálculo. Las promesas permiten esto activamente al ser de naturaleza temporal (sensible al tiempo).

Recapitulación rápida de las promesas

Una promesa es un valor en el tiempo . Las promesas tienen estado, comienzan como pendientes sin valor y pueden conformarse con:

  • cumplido, lo que significa que el cálculo se completó con éxito.
  • rechazado, lo que significa que el cálculo falló.

Una promesa solo puede cambiar de estado una vez, después de lo cual siempre permanecerá en el mismo estado para siempre. then puede adjuntar controladores a las promesas para extraer su valor y manejar los errores. then manejadores permiten encadenar las llamadas. Las promesas se crean mediante el uso de API que las devuelven . Por ejemplo, la fetch reemplazo de AJAX más moderna o las promesas de devolución de $.get jQuery.

Cuando llamamos a .then en una promesa y devolvemos algo de ella, recibimos una promesa por el valor procesado . Si devolvemos otra promesa, obtendremos cosas increíbles, pero sostengamos nuestros caballos.

Con promesas

Veamos cómo podemos resolver el problema anterior con promesas. Primero, demostremos nuestra comprensión de los estados de promesa desde arriba usando el constructor Promise para crear una función de retraso:

function delay(ms){ // takes amount of milliseconds
    // returns a new promise
    return new Promise(function(resolve, reject){
        setTimeout(function(){ // when the time is up
            resolve(); // change the promise to the fulfilled state
        }, ms);
    });
}

Ahora, después de convertir setTimeout para usar promesas, podemos usarlo para que cuente:

function delay(ms){ // takes amount of milliseconds
  // returns a new promise
  return new Promise(function(resolve, reject){
    setTimeout(function(){ // when the time is up
      resolve(); // change the promise to the fulfilled state
    }, ms);
  });
}

function getFive(){
  // we're RETURNING the promise, remember, a promise is a wrapper over our value
  return delay(100).then(function(){ // when the promise is ready
      return 5; // return the value 5, promises are all about return values
  })
}
// we _have_ to wrap it like this in the call site, we can't access the plain value
getFive().then(function(five){ 
   document.body.innerHTML = five;
});

Básicamente, en lugar de devolver un valor que no podemos hacer debido al modelo de concurrencia, estamos devolviendo un contenedor para un valor con el que podemos desenvolverlo . Es como una caja con la que puedes abrir then .

Aplicando esto

Esto es lo mismo para tu llamada original a la API,puedes:

function foo() {
    // RETURN the promise
    return fetch("/echo/json").then(function(response){
        return response.json(); // process it inside the `then`
    });
}

foo().then(function(response){
    // access the value inside the `then`
})

Así que esto funciona igual de bien.Hemos aprendido que no podemos devolver valores de llamadas ya asincrónicas pero podemos usar promesas y encadenarlas para realizar el procesamiento.Ahora sabemos cómo devolver la respuesta de una llamada asíncrona.

ES2015 (ES6)

ES6 introduce generadores que son funciones que pueden regresar en el medio y luego reanudar el punto en el que estaban. Esto suele ser útil para secuencias, por ejemplo:

function* foo(){ // notice the star, this is ES6 so new browsers/node/io only
    yield 1;
    yield 2;
    while(true) yield 3;
}

Es una función que devuelve un iterador sobre la secuencia 1,2,3,3,3,3,.... que se puede iterar. Si bien esto es interesante por sí solo y abre muchas posibilidades, hay un caso interesante en particular.

Si la secuencia que estamos produciendo es una secuencia de acciones en lugar de números, podemos pausar la función cada vez que se produce una acción y esperarla antes de reanudar la función. Entonces, en lugar de una secuencia de números, necesitamos una secuencia de valores futuros , es decir, promesas.

Este truco algo complicado pero muy poderoso nos permite escribir código asincrónico de manera sincrónica. Hay varios "corredores" que hacen esto por usted, escribir uno es unas pocas líneas de código pero está más allá del alcance de esta respuesta. Voy a usar Bluebird's Promise.coroutine aquí, pero hay otros contenedores como co o Q.async .

var foo = coroutine(function*(){
    var data = yield fetch("/echo/json"); // notice the yield
    // code here only executes _after_ the request is done
    return data.json(); // data is defined
});

Este método devuelve una promesa en sí,que podemos consumir de otras cortinas.Por ejemplo:

var main = coroutine(function*(){
   var bar = yield foo(); // wait our earlier coroutine, it returns a promise
   // server call done here, code below executes when done
   var baz = yield fetch("/api/users/"+bar.userid); // depends on foo's result
   console.log(baz); // runs after both requests done
});
main();

ES2016 (ES7)

En ES7, esto está aún más estandarizado, hay varias propuestas en este momento, pero en todas ellas puede await promesa. Esto es solo "azúcar" (sintaxis más agradable) para la propuesta ES6 anterior al agregar el async y await palabras clave. Haciendo el ejemplo anterior:

async function foo(){
    var data = await fetch("/echo/json"); // notice the await
    // code here only executes _after_ the request is done
    return data.json(); // data is defined
}

De todos modos,sigue siendo una promesa :)




Answer 5 Nic


Estás usando Ajax incorrectamente.La idea es que no devuelva nada,sino que pase los datos a algo llamado función de devolución de llamada,que maneja los datos.

Eso es:

function handleData( responseData ) {

    // Do what you want with the data
    console.log(responseData);
}

$.ajax({
    url: "hi.php",
    ...
    success: function ( data, status, XHR ) {
        handleData(data);
    }
});

Devolver cualquier cosa en el controlador de envío no hará nada.En su lugar debes entregar los datos,o hacer lo que quieras con ellos directamente dentro de la función de éxito.