Pregunta ¿Cómo devuelvo la respuesta de una llamada asíncrona?


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

Traté de devolver el valor de la success devolución de llamada, así como asignar la respuesta a una variable local dentro de la función y devolverla, 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`.

4307
2018-01-08 17:06


origen


Respuestas:


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

-> Si ya entiende el problema, salte a las posibles soluciones a continuación.

El problema

los UN en Ajax representa asincrónico . Eso significa que el envío de la solicitud (o más bien la recepción de la respuesta) se saca del flujo de ejecución normal. En tu ejemplo, $.ajax regresa inmediatamente y la siguiente declaración, return result;, se ejecuta antes de la función que aprobó como success devolución de llamada incluso fue llamado.

Aquí hay una analogía que con suerte hace la diferencia entre el flujo de flujo sincrónico y asíncrono:

Sincrónico

Imagine que hace una llamada telefónica a un amigo y le pide que busque algo por usted. Aunque puede llevar un tiempo, esperas por teléfono y miras fijamente al espacio, hasta que tu amigo te dé la respuesta que necesitas.

Lo mismo ocurre cuando realiza una llamada a función que contiene el 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 var item = findItem(); tiene que Espere hasta que la función devuelva el resultado.

Asincrónico

Llamas a tu amigo nuevamente por la misma razón. Pero esta vez le dices que tienes prisa y que debería le devuelva la llamada en tu teléfono móvil. Cuelga, sal de la casa y haz lo que planeas hacer. Una vez que su amigo lo devuelve, está tratando con la información que le dio.

Eso es exactamente lo que sucede cuando haces una solicitud de Ajax.

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

En lugar de esperar la respuesta, la ejecución continúa inmediatamente y se ejecuta la instrucción después de la llamada Ajax. Para obtener la respuesta eventualmente, usted proporciona una función para ser llamado una vez que se recibió la respuesta, una llamar de vuelta (¿notas algo? llamar de vuelta ?). Cualquier instrucción que venga después de esa llamada se ejecuta antes de llamar a la devolución de llamada.


Solución (es)

¡Abraza la naturaleza asíncrona de JavaScript! Mientras que ciertas operaciones asincrónicas proporcionan contrapartes sincrónicas (también lo hace "Ajax"), generalmente se desaconseja su uso, especialmente en un contexto de navegador.

¿Por qué es malo, preguntas?

JavaScript se ejecuta en el subproceso de interfaz de usuario del navegador y cualquier proceso de ejecución larga bloqueará la interfaz de usuario, por lo que no responde. Además, hay un límite superior en el tiempo de ejecución para JavaScript y el navegador le preguntará al usuario si continuará la ejecución o no.

Todo esto es una experiencia de usuario realmente mala. El usuario no podrá decir si todo está funcionando 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 están construyendo una encima de la otra:

  • Promesas con async/await (ES2017 +, disponible en navegadores más antiguos si usa un transpiler o regenerador)
  • Devolución de llamada (popular en el nodo)
  • Promesas con then() (ES2015 +, disponible en navegadores más antiguos si usa una de las muchas bibliotecas prometedoras)

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


ES2017 +: Promesas con async/await

Se presentó la nueva versión de ECMAScript lanzada en 2017 soporte de nivel de sintaxis para funciones asincrónicas. Con la ayuda de async y await, puede escribir asincrónicamente en un "estilo síncrono". Sin embargo, no se equivoque: el código sigue siendo asincrónico, pero es más fácil de leer / comprender.

async/await construye sobre promesas: una async la función siempre devuelve una promesa. await "desenvuelve" una promesa y da como resultado el valor con el que se resolvió la promesa o arroja un error si la promesa fue rechazada.

Importante: Solo puedes usar await dentro de una async función. Eso significa que en el nivel más alto, aún tienes que trabajar directamente con la promesa.

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

Aquí hay un ejemplo que se basa en 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 a second (just for the sake of this example)
    await delay(1000);
    // 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;
  }
}

// Async functions always return a promise
getAllBooks()
  .then(function(books) {
    console.log(books);
  });

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


Deja que las funciones acepten devoluciones de llamada

Una devolución de llamada es simplemente una función pasada 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 devolución de llamada siempre que se realice el proceso asincrónico. Por lo general, el resultado se pasa a la devolución de llamada.

En el ejemplo de la pregunta, puedes hacer foo aceptar una devolución de llamada y usarla como success llamar de vuelta. Así que esto

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

se convierte

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

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

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

foo(myCallback);

foo en sí mismo se define de la siguiente manera:

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

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

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

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

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


ES2015 +: Promesas con entonces()

los Promise API es una nueva característica de ECMAScript 6 (ES2015), pero tiene buenas soporte de navegador ya. 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 las funciones asíncronas (p. azulejo)

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

La ventaja sobre las devoluciones de llamada simples es que le permiten desacoplar su código y son más fáciles de componer.

Aquí hay un ejemplo simple de 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 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 prometen ofrecer está más allá del alcance de esta respuesta, pero si escribe un código nuevo, debe considerarlo seriamente. Proporcionan una gran abstracción y separación de su código.

Más información sobre promesas: Rocas HTML5 - Promesas de JavaScript

Nota al margen: objetos diferidos de jQuery

Objetos diferidos son la implementación personalizada de promesas de jQuery (antes de que la API Promesa fuera estandarizada). Se comportan casi como las promesas, pero exponen una API ligeramente diferente.

Todos los métodos Ajax de jQuery ya devuelven un "objeto diferido" (en realidad una promesa de un objeto diferido) que puede devolver desde su función:

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

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

Nota al margen: ganas de promesa

Tenga en cuenta que las promesas y los objetos diferidos son contenedores para un valor futuro, no son el valor en sí mismo. Por ejemplo, supongamos 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 entiende los problemas de asincronía anteriores. Específicamente, $.ajax() no congela el código mientras revisa 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 if declaración va a obtener siempre este objeto diferido, trátelo como true, y proceda como si el usuario estuviera conectado. No es bueno.

Pero la solución 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 recomendado: llamadas síncronas "Ajax"

Como mencioné, algunas (!) Operaciones asincrónicas tienen contrapartes síncronas. No defiendo su uso, pero para mayor completitud, así es como realizaría una llamada sincrónica:

Sin jQuery

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

jQuery

Si utiliza jQuery, puedes configurar el async opción de false. Tenga en cuenta que esta opción es obsoleto desde jQuery 1.8. Entonces puede seguir usando un success devolución de llamada o acceder al responseText propiedad de la objeto jqXHR:

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

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

¡Aviso! No es posible hacer una sincronización JSONP solicitud. JSONP, por su propia naturaleza, siempre es asincrónico (una razón más para no considerar esta opción).


4654
2018-01-08 17:06



Si eres no usando jQuery en tu código, esta respuesta es para ti

Tu código debe ser algo así como 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'

Félix Kling hizo un buen trabajo al escribir una respuesta para las personas que usan jQuery para AJAX, he decidido ofrecer una alternativa para las personas que no lo son.

(Nota, para aquellos que usan el nuevo fetch API, Angular o promesas He agregado otra respuesta a continuación)


A lo que te enfrentas

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

los UN en AJAX significa asincrónico. Eso significa que el envío de la solicitud (o más bien la recepción de la respuesta) se saca del flujo de ejecución normal. En tu ejemplo, .send regresa inmediatamente y la siguiente declaración, return result;, se ejecuta antes de la función que aprobó como success devolución de llamada incluso fue llamado.

Esto significa que cuando regresa, el oyente que ha definido aún no se ejecutó, lo que significa que el valor que está devolviendo no se ha definido.

Aquí hay una analogía simple

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

(Violín)

El valor de a devuelto es undefined ya que el a=5 la parte 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 reactivamente , 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);
}

Se llama CPS. Básicamente, estamos pasando getFive una acción para realizar cuando se completa, 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).

Soluciones posibles

Básicamente, hay dos formas de resolver esto:

  1. Haga que la llamada AJAX sea sincrónica (vamos a llamarla SJAX).
  2. Reestructura tu código para que funcione correctamente con devoluciones de llamadas.

1. AJAX sincrónico - ¡No lo hagas!

En cuanto a AJAX síncrono, no lo hagas! La respuesta de Félix plantea algunos argumentos convincentes sobre por qué es una mala idea. En resumen, 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 admite comunicaciones síncronas y asíncronas. En general, sin embargo, las solicitudes asincrónicas deberían preferirse a las solicitudes síncronas por motivos de rendimiento.

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

Si tu tener para hacerlo, puedes pasar una bandera: Aquí es cómo:

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

Deja que tu función acepte una devolución de llamada. En el código de ejemplo foo se puede hacer para aceptar una devolución de llamada. Le diremos a nuestro código cómo reaccionar cuando foo completa.

Asi que:

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

Se convierte en:

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

Aquí pasamos una función anónima, pero igual podríamos pasar una referencia a una función existente, haciendo que se vea así:

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

Para obtener más detalles sobre cómo se realiza este tipo de diseño de devolución de llamada, consulte la respuesta de Felix.

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

(violín)

Ahora hemos hecho que nuestra función foo acepte una acción para ejecutar cuando el AJAX se complete con éxito, podemos extender esto más al verificar si el estado de la respuesta no es 200 y actuar en consecuencia (crear un controlador de fallas y tal). Resolviendo nuestro problema de manera efectiva.

Si aún te cuesta entender esto lea la guía de introducción de AJAX en MDN.


886
2018-05-29 23:30



XMLHttpRequest 2 (antes que nada lea las respuestas de Benjamin Gruenbaum y Felix Kling)

Si no utiliza jQuery y quiere un pequeño XMLHttpRequest 2 que funcione en los navegadores modernos y también en los navegadores móviles, sugiero usarlo 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 puedes ver:

  1. Es más corto que todas las demás funciones enumeradas.
  2. La devolución de llamada se establece directamente (por lo que no hay cierres adicionales innecesarios).
  3. Utiliza la nueva carga (por lo que no es necesario comprobar el estado de estado listo &&)
  4. Hay algunas otras situaciones que no recuerdo que hacen que XMLHttpRequest 1 sea molesto.

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

Lo más simple:

this.response

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

e.target.response

Ejemplo:

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

O (lo anterior es mejor las funciones anónimas siempre son 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.

Revisa Características avanzadas de XMLHttpRequest

Es compatible con todos * navegadores modernos. Y puedo confirmar que estoy usando este enfoque ya que XMLHttpRequest 2 existe. Nunca tuve ningún tipo de problema en todos los navegadores que uso.

onreadystatechange solo es útil si desea obtener los encabezados en el estado 2.

Utilizando el XMLHttpRequest El nombre de la variable es otro gran error, ya que necesita ejecutar la devolución de llamada dentro de los cierres onload / oreadystatechange, de lo contrario lo perdió.


Ahora, si desea algo más complejo utilizando post y FormData, puede 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)
}

De nuevo ... es una función muy corta, pero sí recibe 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 forma completa (document.getElementsByTagName('form')[0])

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

O establece algunos valores personalizados:

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

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

Habiendo dicho eso ... ¿por qué no hacerlo de la manera fácil?


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

Manejador 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 script anterior, tiene un controlador de errores que está estáticamente definido para que no comprometa la función. El controlador de errores también se puede usar para otras funciones.

Pero para realmente salir un error, solamente es escribir una URL incorrecta en cuyo caso cada navegador arroja un error.

Los manejadores de errores pueden ser útiles si configura encabezados personalizados, configura el tipo de respuesta para blob array buffer o lo que sea ....

Incluso si pasa 'POSTAPAPAP' como método, no arrojará un error.

Incluso si pasa 'fdggdgilfdghfldj' como formdata no arrojará un error.

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

En el segundo caso, simplemente funciona. Debe verificar en el lado del servidor si pasó los datos de la publicación correcta.

dominio cruzado no permitido arroja error automáticamente.

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

Solo está el 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().

Entonces: no es necesario verificar los errores si puede copiar y pegar la URL correctamente. ;)

PD: Como la primera prueba, escribí x ('x', displayAjax) ..., ¿y obtuvo una respuesta totalmente ... ??? Así que revisé 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Á. LOL'd


Leer un archivo sincrónico

No hagas eso.

Si desea bloquear el navegador por un tiempo, cargue un buen archivo txt grande 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 manera de hacer esto de una manera no asincrónica. (Sí, con setTimeout loop ... ¿pero en serio?)

Otro punto es ... si trabajas con API o solo con los propios archivos de la lista o lo que sea, siempre utilizas diferentes funciones para cada solicitud ...

Solo si tiene una página donde carga siempre el mismo XML / JSON o lo que sea, solo necesita una función. En ese caso, modifique un poco la función Ajax y reemplace b con su función especial.


Las funciones anteriores son para uso básico.

Si desea EXTENDER la función ...

Sí tu puedes.

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

Pero puedes hacer muchas cosas con XMLHttpRequest 2:

Creé un administrador de descargas (usando rangos en ambos lados con currículum, lector de archivos, sistema de archivos), varios convertidores de resizer de imágenes usando canvas, rellenando bases de datos websql con base64images y mucho más ... Pero en estos casos debes crear una función solo para ese propósito ... a veces necesitas un blob, buffers de matriz, puedes establecer encabezados, anular el tipo mimet y hay mucho más ...

Pero la pregunta aquí es cómo devolver una respuesta Ajax ... (Agregué una manera fácil).


302
2017-08-19 08:06



Si está utilizando promesas, esta respuesta es para usted.

Esto significa AngularJS, jQuery (con diferido), reemplazo XHR nativo (fetch), EmberJS, guardar de BackboneJS o cualquier biblioteca de nodos que devuelve promesas.

Tu código debe ser algo así como 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.

Félix Kling hizo un buen trabajo al escribir una respuesta para las personas que usan jQuery con devoluciones de llamada para AJAX. Tengo una respuesta para XHR nativo. Esta respuesta es para el uso genérico de las promesas, ya sea en la interfaz o back-end.


El problema central

El modelo de simultaneidad de JavaScript en el navegador y en el servidor con NodeJS / io.js es asincrónico y reactivo.

Cada vez que llamas a un método que devuelve una promesa, el then los manejadores son siempre ejecutado de forma asincrónica, es decir, después el código debajo de ellos que no está en una .then entrenador de animales.

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

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

    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 data es undefined ya que el data = 5 la parte aún no se ha ejecutado. Es probable que se ejecute en un segundo, pero en ese momento es irrelevante para el valor devuelto.

Dado que la operación no se realizó aún (AJAX, llamada al servidor, IO, temporizador) está devolviendo el valor antes de que la solicitud tuviera la oportunidad de decirle a su código cuál es ese valor.

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

Recapitulación rápida de las promesas

Una promesa es una valor a lo largo del 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 los estados una vez después de lo cual siempre permanecerá en el mismo estado para siempre. Puedes unir then manejadores a promesas para extraer su valor y manejar errores. then los manejadores permiten encadenamiento de llamadas. Las promesas son creadas por usando APIs que los devuelven. Por ejemplo, el reemplazo AJAX más moderno fetch o jQuery's $.get promesas de retorno.

Cuando llamamos .then en una promesa y regreso algo de eso, obtenemos una promesa para 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 utilizando el Promise constructor para crear una función de retardo:

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 usar then para hacer 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 lo cual no podemos hacer debido al modelo de simultaneidad, estamos devolviendo un envoltura por un valor que podemos desenvolver con then. Es como una caja con la que puedes abrir then.

Aplicando esto

Esto es igual para su llamada API original, usted puede:

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

Entonces 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 asincrónica.

ES2015 (ES6)

ES6 presenta generadores que son funciones que pueden regresar en el medio y luego reanudar el punto en el que estaban. Esto es típicamente ú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 puede ser iterado Si bien esto es interesante por sí mismo y abre espacio para 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 siempre que se produzca una acción y esperarla antes de reanudar la función. Entonces, en lugar de una secuencia de números, necesitamos una secuencia de futuro valores, es decir: promesas.

Este truco algo complicado pero muy poderoso nos permite escribir código asíncrono de forma 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. Estaré usando Bluebird Promise.coroutine aquí, pero hay otras envolturas 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í misma, que podemos consumir de otras corutinas. 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á más estandarizado, hay varias propuestas en este momento, pero en todas ellas se puede await promesa. Esto es solo "azúcar" (sintaxis más agradable) para la propuesta de ES6 anterior agregando el async y await palabras clave. Haciendo el ejemplo de arriba:

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
}

Todavía devuelve una promesa igual :)


243
2018-05-12 02:22



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

Es decir:

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 algo en el controlador de envío no hará nada. En su lugar, debe entregar los datos o hacer lo que quiera con ellos directamente dentro de la función de éxito.


192
2018-05-23 02:05



La solución más simple es crear una función de JavaScript y llamarla para Ajax success llamar de vuelta.

function callServerAsync(){
    $.ajax({
        url: '...',
        success: function(response) {

            successCallback(response);
        }
    });
}

function successCallback(responseObj){
    // Do something like read the response and show data
    alert(JSON.stringify(responseObj)); // Only applicable to JSON response
}

function foo(callback) {

    $.ajax({
        url: '...',
        success: function(response) {
           return callback(null, response);
        }
    });
}

var result = foo(function(err, result){
          if (!err)
           console.log(result);    
}); 

184
2018-02-18 18:58



Voy a responder con un cómic de aspecto horrible y dibujado a mano. La segunda imagen es la razón por la cual result es undefined en tu ejemplo de código.

enter image description here


154
2017-08-11 14:17



Angular1

Para las personas que están usando AngularJS, puede manejar esta situación usando Promises.

aquí dice,

Las promesas se pueden usar para desenredar funciones asíncronas y permiten encadenar múltiples funciones juntas.

Puedes encontrar una buena explicación aquí además.

Ejemplo encontrado en documentos mencionado abajo.

  promiseB = promiseA.then(
    function onSuccess(result) {
      return result + 1;
    }
    ,function onError(err) {
      //Handle error
    }
  );

 // promiseB will be resolved immediately after promiseA is resolved 
 // and its value will be the result of promiseA incremented by 1.

Angular2 y posterior

En Angular2 con mirar el siguiente ejemplo, pero es recomendado usar Observables con Angular2.

 search(term: string) {
     return this.http
  .get(`https://api.spotify.com/v1/search?q=${term}&type=artist`)
  .map((response) => response.json())
  .toPromise();

}

Puedes consumir eso de esta manera,

search() {
    this.searchService.search(this.searchField.value)
      .then((result) => {
    this.result = result.artists.items;
  })
  .catch((error) => console.error(error));
}

Ver el original publicar aquí. Pero Typescript no es compatible es6 promesas nativas, si desea usarlo, es posible que necesite un complemento para eso.

Además aquí están las promesas especulación definir aquí.


113
2017-08-26 08:11



La mayoría de las respuestas aquí brindan sugerencias útiles para cuando tiene una única operación asincrónica, pero a veces, esto aparece cuando necesita hacer una operación asincrónica para cada entrada en una matriz u otra estructura tipo lista. La tentación es hacer esto:

// WRONG
var results = [];
theArray.forEach(function(entry) {
    doSomethingAsync(entry, function(result) {
        results.push(result);
    });
});
console.log(results); // E.g., using them, returning them, etc.

Ejemplo:

// WRONG
var theArray = [1, 2, 3];
var results = [];
theArray.forEach(function(entry) {
    doSomethingAsync(entry, function(result) {
        results.push(result);
    });
});
console.log("Results:", results); // E.g., using them, returning them, etc.

function doSomethingAsync(value, callback) {
    console.log("Starting async operation for " + value);
    setTimeout(function() {
        console.log("Completing async operation for " + value);
        callback(value * 2);
    }, Math.floor(Math.random() * 200));
}
.as-console-wrapper {
  max-height: 100% !important;
}

La razón por la que esto no funciona es que las devoluciones de llamada de doSomethingAsync no se han ejecutado aún cuando intentas usar los resultados.

Entonces, si tiene una matriz (o una lista de algún tipo) y desea realizar operaciones asíncronas para cada entrada, tiene dos opciones: realizar las operaciones en paralelo (superposición) o en serie (una tras otra en secuencia).

Paralela

Puede comenzar todas ellas y realizar un seguimiento de la cantidad de devoluciones de llamada que está esperando, y luego usar los resultados cuando haya obtenido tantas devoluciones de llamada:

var results = [];
var expecting = theArray.length;
theArray.forEach(function(entry, index) {
    doSomethingAsync(entry, function(result) {
        results[index] = result;
        if (--expecting === 0) {
            // Done!
            console.log("Results:", results); // E.g., using the results
        }
    });
});

Ejemplo:

var theArray = [1, 2, 3];
var results = [];
var expecting = theArray.length;
theArray.forEach(function(entry, index) {
    doSomethingAsync(entry, function(result) {
        results[index] = result;
        if (--expecting === 0) {
            // Done!
            console.log("Results:", results); // E.g., using the results
        }
    });
});

function doSomethingAsync(value, callback) {
    console.log("Starting async operation for " + value);
    setTimeout(function() {
        console.log("Completing async operation for " + value);
        callback(value * 2);
    }, Math.floor(Math.random() * 200));
}
.as-console-wrapper {
  max-height: 100% !important;
}

(Podríamos eliminarlo expecting y solo usa results.length === theArray.length, pero eso nos deja abiertos a la posibilidad de que theArray se cambia mientras las llamadas son excepcionales ...)

Observe cómo usamos el index de forEach para guardar el resultado en results en la misma posición que la entrada a la que se refiere, incluso si los resultados llegan fuera de orden (ya que las llamadas asincrónicas no se completan necesariamente en el orden en que se iniciaron).

Pero, ¿y si necesitas regreso esos resultados de una función? Como han señalado las otras respuestas, no puedes; tienes que tener tu función de aceptar y llamar a una devolución de llamada (o devolver un Promesa) Aquí hay una versión de devolución de llamada:

function doSomethingWith(theArray, callback) {
    var results = [];
    var expecting = theArray.length;
    theArray.forEach(function(entry, index) {
        doSomethingAsync(entry, function(result) {
            results[index] = result;
            if (--expecting === 0) {
                // Done!
                callback(results);
            }
        });
    });
}
doSomethingWith(theArray, function(results) {
    console.log("Results:", results);
});

Ejemplo:

function doSomethingWith(theArray, callback) {
    var results = [];
    var expecting = theArray.length;
    theArray.forEach(function(entry, index) {
        doSomethingAsync(entry, function(result) {
            results[index] = result;
            if (--expecting === 0) {
                // Done!
                callback(results);
            }
        });
    });
}
doSomethingWith([1, 2, 3], function(results) {
    console.log("Results:", results);
});

function doSomethingAsync(value, callback) {
    console.log("Starting async operation for " + value);
    setTimeout(function() {
        console.log("Completing async operation for " + value);
        callback(value * 2);
    }, Math.floor(Math.random() * 200));
}
.as-console-wrapper {
  max-height: 100% !important;
}

O aquí está una versión que devuelve Promise en lugar:

function doSomethingWith(theArray) {
    return new Promise(function(resolve) {
        var results = [];
        var expecting = theArray.length;
        theArray.forEach(function(entry, index) {
            doSomethingAsync(entry, function(result) {
                results[index] = result;
                if (--expecting === 0) {
                    // Done!
                    resolve(results);
                }
            });
        });
    });
}
doSomethingWith(theArray).then(function(results) {
    console.log("Results:", results);
});

Por supuesto si doSomethingAsync nos pasó errores, lo usamos reject rechazar la promesa cuando recibimos un error.)

Ejemplo:

function doSomethingWith(theArray) {
    return new Promise(function(resolve) {
        var results = [];
        var expecting = theArray.length;
        theArray.forEach(function(entry, index) {
            doSomethingAsync(entry, function(result) {
                results[index] = result;
                if (--expecting === 0) {
                    // Done!
                    resolve(results);
                }
            });
        });
    });
}
doSomethingWith([1, 2, 3]).then(function(results) {
    console.log("Results:", results);
});

function doSomethingAsync(value, callback) {
    console.log("Starting async operation for " + value);
    setTimeout(function() {
        console.log("Completing async operation for " + value);
        callback(value * 2);
    }, Math.floor(Math.random() * 200));
}
.as-console-wrapper {
  max-height: 100% !important;
}

(O alternativamente, podrías hacer un contenedor para doSomethingAsync eso devuelve una promesa, y luego haz lo siguiente ...)

Si doSomethingAsync te da un Promesa, puedes usar Promise.all:

function doSomethingWith(theArray) {
    return Promise.all(theArray.map(function(entry) {
        return doSomethingAsync(entry, function(result) {
            results.push(result);
        });
    }));
}
doSomethingWith(theArray).then(function(results) {
    console.log("Results:", results);
});

Ejemplo:

function doSomethingWith(theArray) {
    return Promise.all(theArray.map(function(entry) {
        return doSomethingAsync(entry, function(result) {
            results.push(result);
        });
    }));
}
doSomethingWith([1, 2, 3]).then(function(results) {
    console.log("Results:", results);
});

function doSomethingAsync(value) {
    console.log("Starting async operation for " + value);
    return new Promise(function(resolve) {
        setTimeout(function() {
            console.log("Completing async operation for " + value);
            resolve(value * 2);
        }, Math.floor(Math.random() * 200));
    });
}
.as-console-wrapper {
  max-height: 100% !important;
}

Tenga en cuenta que Promise.all resuelve su promesa con una matriz de los resultados de todas las promesas que le das cuando están todas resueltas, o rechaza su promesa cuando el primero de las promesas que le das rechaza.

Serie

¿Supongamos que no quiere que las operaciones estén en paralelo? Si desea ejecutarlos uno tras otro, debe esperar a que se complete cada operación antes de comenzar la siguiente. Aquí hay un ejemplo de una función que hace eso y llama a una devolución de llamada con el resultado:

function doSomethingWith(theArray, callback) {
    var results = [];
    doOne(0);
    function doOne(index) {
        if (index < theArray.length) {
            doSomethingAsync(theArray[index], function(result) {
                results.push(result);
                doOne(index + 1);
            });
        } else {
            // Done!
            callback(results);
        }
    }
}
doSomethingWith(theArray, function(results) {
    console.log("Results:", results);
});

(Ya que estamos haciendo el trabajo en serie, podemos simplemente usar results.push(result) ya que sabemos que no obtendremos resultados fuera de orden. En lo anterior podríamos haber usado results[index] = result;, pero en algunos de los siguientes ejemplos no tenemos un índice para usar).

Ejemplo:

function doSomethingWith(theArray, callback) {
    var results = [];
    doOne(0);
    function doOne(index) {
        if (index < theArray.length) {
            doSomethingAsync(theArray[index], function(result) {
                results.push(result);
                doOne(index + 1);
            });
        } else {
            // Done!
            callback(results);
        }
    }
}
doSomethingWith([1, 2, 3], function(results) {
    console.log("Results:", results);
});

function doSomethingAsync(value, callback) {
    console.log("Starting async operation for " + value);
    setTimeout(function() {
        console.log("Completing async operation for " + value);
        callback(value * 2);
    }, Math.floor(Math.random() * 200));
}
.as-console-wrapper {
  max-height: 100% !important;
}

(O, de nuevo, crea un contenedor para doSomethingAsync eso te da una promesa y haz lo siguiente ...)

Si doSomethingAsync te da una Promesa, si puedes usar la sintaxis ES2017 + (tal vez con un transpiler como Babel), puedes usar un async función con for-of y await:

async function doSomethingWith(theArray) {
    const results = [];
    for (const entry of theArray) {
        results.push(await doSomethingAsync(entry));
    }
    return results;
}
doSomethingWith(theArray).then(results => {
    console.log("Results:", results);
});

Ejemplo:

async function doSomethingWith(theArray) {
    const results = [];
    for (const entry of theArray) {
        results.push(await doSomethingAsync(entry));
    }
    return results;
}
doSomethingWith([1, 2, 3]).then(function(results) {
    console.log("Results:", results);
});

function doSomethingAsync(value) {
    console.log("Starting async operation for " + value);
    return new Promise(function(resolve) {
        setTimeout(function() {
            console.log("Completing async operation for " + value);
            resolve(value * 2);
        }, Math.floor(Math.random() * 200));
    });
}
.as-console-wrapper {
  max-height: 100% !important;
}

Si no puede usar la sintaxis ES2017 + (aún), puede usar una variación en el Patrón "Promesa Reducir" (Esto es más complejo que la reducción Promesa habitual porque no pasamos el resultado de uno al siguiente, sino que recopilamos sus resultados en una matriz):

function doSomethingWith(theArray) {
    return theArray.reduce(function(p, entry) {
        return p.then(function(results) {
            return doSomethingAsync(entry).then(function(result) {
                results.push(result);
                return results;
            });
        });
    }, Promise.resolve([]));
}
doSomethingWith(theArray).then(function(results) {
    console.log("Results:", results);
});

Ejemplo:

function doSomethingWith(theArray) {
    return theArray.reduce(function(p, entry) {
        return p.then(function(results) {
            return doSomethingAsync(entry).then(function(result) {
                results.push(result);
                return results;
            });
        });
    }, Promise.resolve([]));
}
doSomethingWith([1, 2, 3]).then(function(results) {
    console.log("Results:", results);
});

function doSomethingAsync(value) {
    console.log("Starting async operation for " + value);
    return new Promise(function(resolve) {
        setTimeout(function() {
            console.log("Completing async operation for " + value);
            resolve(value * 2);
        }, Math.floor(Math.random() * 200));
    });
}
.as-console-wrapper {
  max-height: 100% !important;
}

... que es menos engorroso con Funciones de flecha ES2015 +:

function doSomethingWith(theArray) {
    return theArray.reduce((p, entry) => p.then(results => doSomethingAsync(entry).then(result => {
        results.push(result);
        return results;
    })), Promise.resolve([]));
}
doSomethingWith(theArray).then(results => {
    console.log("Results:", results);
});

Ejemplo:

function doSomethingWith(theArray) {
    return theArray.reduce((p, entry) => p.then(results => doSomethingAsync(entry).then(result => {
        results.push(result);
        return results;
    })), Promise.resolve([]));
}
doSomethingWith([1, 2, 3]).then(function(results) {
    console.log("Results:", results);
});

function doSomethingAsync(value) {
    console.log("Starting async operation for " + value);
    return new Promise(function(resolve) {
        setTimeout(function() {
            console.log("Completing async operation for " + value);
            resolve(value * 2);
        }, Math.floor(Math.random() * 200));
    });
}
.as-console-wrapper {
  max-height: 100% !important;
}


90
2018-05-03 16:59