Cómo devolver la respuesta de una llamada asincrónica

6151

Tengo una función fooque realiza una solicitud asincrónica. ¿Cómo puedo devolver la respuesta / resultado de foo?

Estoy tratando de devolver el valor de la devolución de llamada, así como de asignar el resultado a una variable local dentro de la función y devolver esa, pero ninguna de esas formas en realidad devuelve la respuesta (todas devuelven undefinedo cualquiera que sea el valor inicial de la variable resultes).

Ejemplo de una función asíncrona que acepta una devolución de llamada (usando la ajaxfunción de jQuery )

function foo() {
    var result;

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

    return result; // It always returns `undefined`
}

Ejemplo usando Node.js:

function foo() {
    var result;

    fs.readFile("path/to/file", function(err, data) {
        result = data;
        // return data; // <- I tried that one as well
    });

    return result; // It always returns `undefined`
}

Ejemplo usando el thenbloque de una promesa:

function foo() {
    var result;

    fetch(url).then(function(response) {
        result = response;
        // return response; // <- I tried that one as well
    });

    return result; // It always returns `undefined`
}
6
  • use deasync como este stackoverflow.com/a/47051880/2083877 8 de septiembre a las 11:26
  • 6
    @SunilKumar No creo que esto sea útil. OP hizo esta pregunta y la auto-respuesta para documentar cómo obtener la respuesta de las llamadas asíncronas. Sugerir un módulo de terceros frustra ese propósito y, en mi opinión, el paradigma introducido por ese módulo no es una buena práctica.
    Seblor
    10 de septiembre a las 9:14
  • ¿Es hora de deshacerse de jQuery en esta pregunta? ¿Es bastante legado en 2021?
    Liam
    15 oct a las 14:10
  • 1
    @Liam: Es solo un ejemplo de una función asincrónica que acepta una devolución de llamada. 15 oct a las 14:13
  • Tiene sentido, hice un ajuste en el título para restar importancia a jQuery
    Liam
    15 oct a las 14:36
6205
+150

→ For a more general explanation of asynchronous behaviour with different examples, see Why is my variable unaltered after I modify it inside of a function? - Asynchronous code reference

→ If you already understand the problem, skip to the possible solutions below.

El problema

La A en Ajax significa asincrónico . Eso significa que enviar la solicitud (o más bien recibir la respuesta) se saca del flujo de ejecución normal. En su ejemplo, $.ajaxregresa inmediatamente y la siguiente instrucción return result;,, se ejecuta antes de que successse llame a la función que pasó como devolución de llamada.

Aquí hay una analogía que, con suerte, hace que la diferencia entre el flujo sincrónico y asincrónico sea más clara:

Sincrónico

Imagina que haces una llamada telefónica a un amigo y le pides que busque algo para ti. Aunque puede llevar un tiempo, esperas en el teléfono y miras fijamente hasta que tu amigo te da la respuesta que necesitabas.

Lo mismo sucede cuando realiza una llamada a una 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 findItempuede tardar mucho en ejecutarse, cualquier código que venga después var item = findItem();tiene que esperar hasta que la función devuelva el resultado.

Asincrónico

Vuelve a llamar a su amigo por la misma razón. Pero esta vez le dice que tiene prisa y que debería devolverle la llamada a su teléfono móvil. Cuelgas, sales de casa y haces lo que planeas hacer. Una vez que tu amigo te devuelve la llamada, estás tratando con la información que te dio.

Eso es exactamente lo que sucede cuando realiza una solicitud Ajax.

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

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


Solución (s)

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

¿Por qué es malo preguntas?

JavaScript se ejecuta en el subproceso de la interfaz de usuario del navegador y cualquier proceso de ejecución prolongada bloqueará la interfaz de usuario y dejará de responder. Además, existe un límite superior en el tiempo de ejecución de JavaScript y el navegador le preguntará al usuario si continuar con la ejecución o no.

Todo esto da como resultado una experiencia de usuario realmente mala. 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 están construyendo una encima de la otra:

  • Promesas conasync/await (ES2017 +, disponible en navegadores más antiguos si usa un transpilador o regenerador)
  • Devolución de llamada (popular en el nodo)
  • Promesas conthen() (ES2015 +, disponible en navegadores más 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 de ECMAScript lanzada en 2017 introdujo soporte de nivel de sintaxis para funciones asincrónicas. Con la ayuda de asyncy await, puede escribir asincrónico en un "estilo sincrónico". El código sigue siendo asincrónico, pero es más fácil de leer / comprender.

async/awaitse basa en promesas: una asyncfunció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 puede usar awaitdentro de una asyncfunción. En este momento, el nivel superior awaitaún no es compatible, por lo que es posible que deba realizar un IIFE ( expresión de función inmediatamente invocada ) asíncrona para iniciar un asynccontexto.

Puede leer más sobre asyncy awaitsobre MDN.

Aquí hay un ejemplo que elabora la función de retardofindItem() 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);
})();

Compatibilidad con las versiones actuales del navegador y del nodoasync/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 ).


Permitir que las funciones acepten devoluciones de llamada

Una devolución de llamada es cuando la función 1 se pasa a la función 2. La función 2 puede llamar a la función 1 siempre que esté lista. En el contexto de un proceso asincrónico, la devolución de llamada se llamará 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, puede fooaceptar una devolución de llamada y utilizarla como successdevolución de llamada. Así que esto

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

se convierte en

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

callbackse referirá a la función a la que pasamos foocuando la llamamos y se la pasamos success. Es decir, una vez que la solicitud de Ajax sea exitosa, $.ajaxllamará callbacky pasará la respuesta a la devolución de llamada (a la que se puede hacer referencia 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 usando devoluciones de llamada de lo que parece. Después de todo, JavaScript en el navegador está fuertemente impulsado por eventos (eventos DOM). Recibir la respuesta de Ajax no es más que un evento. Pueden surgir dificultades cuando tenga que trabajar con código de terceros, pero la mayoría de los problemas se pueden resolver con solo pensar en el flujo de la aplicación.


ES2015 +: Promesas con entonces ()

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

Las promesas son contenedores de valores futuros . Cuando la promesa recibe el valor (se resuelve ) o cuando se cancela ( rechaza ), notifica a todos sus "oyentes" que quieren 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.

A continuación, se muestra un ejemplo del uso de 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).
  });
.as-console-wrapper { max-height: 100% !important; top: 0; }

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("https://jsonplaceholder.typicode.com/todos/1")
  .then(function(result) {
    console.log(result); // Code depending on result
  })
  .catch(function() {
    // An error occurred
  });
.as-console-wrapper { max-height: 100% !important; top: 0; }

Describir todas las ventajas que ofrece la promesa está más allá del alcance de esta respuesta, pero si escribe un código nuevo, debe considerarlas seriamente. Proporcionan una gran abstracción y separación de su código.

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

Nota al margen: objetos diferidos de jQuery

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

Cada método Ajax de jQuery ya devuelve 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: trampas de promesas

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 asincrónicos 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, devuelve inmediatamente un objeto jQuery Ajax Deferred, no la respuesta del servidor. Eso significa que la ifdeclaración siempre obtendrá este objeto diferido, lo tratará como truey procederá como si el usuario hubiera iniciado sesión. 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 "Ajax" síncronas

Como mencioné, algunas (!) Operaciones asincrónicas tienen contrapartes sincrónicas. No abogo por su uso, pero en aras de la integridad, así es como realizaría una llamada sincrónica:

Sin jQuery

Si usa directamente un XMLHttpRequestobjeto, pase falsecomo tercer argumento a .open.

jQuery

Si usa jQuery , puede establecer la asyncopción en false. Tenga en cuenta que esta opción está obsoleta desde jQuery 1.8. A continuación, puede seguir utilizando una successdevolución de llamada o acceder a la responseTextpropiedad del objeto jqXHR :

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

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

¡Aviso! No es posible realizar una solicitud JSONP síncrona . JSONP por su propia naturaleza es siempre asincrónico (una razón más para ni siquiera considerar esta opción).

7
  • 87
    @Pommy: Si quieres usar jQuery, debes incluirlo. Consulte docs.jquery.com/Tutorials:Getting_Started_with_jQuery . 17 de enero de 2013 a las 10:47
  • 17
    En la Solución 1, sub jQuery, no pude entender esta línea: If you use any other jQuery AJAX method, such as $.get, $.getJSON, etc., you have them to $.ajax.(Sí, me doy cuenta de que mi nick es un poco irónico en este caso) 6 feb 2013 a las 21:07
  • 39
    @gibberish: Mmmh, no sé cómo se puede aclarar. ¿Ves cómo foose llama y se le pasa una función ( foo(function(result) {....});)? resultse utiliza dentro de esta función y es la respuesta a la solicitud de Ajax. Para hacer referencia a esta función, se llama al primer parámetro de foo callbacky se le asigna en successlugar de una función anónima. Entonces, $.ajaxllamará callbackcuando la solicitud sea exitosa. Traté de explicarlo un poco más. 6 de febrero de 2013 a las 23:29
  • 50
    El chat para esta pregunta está muerto, así que no estoy seguro de dónde proponer los cambios descritos, pero propongo: 1) Cambiar la parte sincrónica a una simple discusión de por qué es mala sin un ejemplo de código de cómo hacerlo. 2) Elimine / combine los ejemplos de devolución de llamada para mostrar solo el enfoque diferido más flexible, que creo que también puede ser un poco más fácil de seguir para aquellos que están aprendiendo Javascript. 16 de abril de 2013 a las 2:45
  • 19
    @Jessi: Creo que entendiste mal esa parte de la respuesta. No puede usar $.getJSONsi desea que la solicitud Ajax sea sincrónica. Sin embargo, no debería querer que la solicitud sea síncrona, por lo que eso no se aplica. Debería utilizar devoluciones de llamada o promesas para manejar la respuesta, como se explicó anteriormente en la respuesta. 8/10/15 a las 17:44
1153

Si estás no usando jQuery en el código, esta respuesta es para usted

Su 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 las personas que usan jQuery para AJAX, pero he decidido proporcionar una alternativa para las personas que no lo están.

( Tenga en cuenta que para aquellos que usan la nueva fetchAPI, Angular o promesas, agregué 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á seguro después de leer esto, léalo.

La A en AJAX significa asincrónico . Eso significa que enviar la solicitud (o más bien recibir la respuesta) se saca del flujo de ejecución normal. En su ejemplo, .sendregresa inmediatamente y la siguiente instrucción return result;,, se ejecuta antes de que successse llame a la función que pasó como devolución de llamada.

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 adevuelto es undefinedporque la a=5pieza 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 , indicá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, estamos pasando getFiveuna 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);

Lo que debería alertar a "5" en la pantalla. (Violín) .

Soluciones posibles

Básicamente, hay dos formas de resolver esto:

  1. Haga que la llamada AJAX sea sincrónica (llamémosla SJAX).
  2. Reestructura tu código para que funcione correctamente con devoluciones de llamada.

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

En cuanto a AJAX sincrónico, ¡no lo hagas! La respuesta de Felix 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 supports both synchronous and asynchronous communications. In general, however, asynchronous requests should be preferred to synchronous requests for performance reasons.

In short, synchronous requests block the execution of code... ...this can cause serious issues...

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. Código de reestructuración

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

Entonces:

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 podríamos pasar fácilmente 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 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();
}

(violín)

Ahora hemos hecho que nuestra función foo acepte una acción para ejecutarse cuando AJAX se complete con éxito. Podemos extender esto aún más verificando si el estado de respuesta no es 200 y actuando en consecuencia (cree un manejador de fallas y demás). Efectivamente, está resolviendo nuestro problema.

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

1
  • 25
    "Las solicitudes síncronas bloquean la ejecución de código y pueden perder memoria y eventos" ¿Cómo puede una solicitud síncrona perder memoria? 16 de agosto de 2013 a las 5:54
424

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

Si no usa jQuery y desea un XMLHttpRequest 2 corto y agradable 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 se puede 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 tiene que verificar el estado de readystate &&)
  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 de var XMLHttpRequest):

Lo más simple:

this.response

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

e.target.response

Ejemplo:

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

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

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

Nada mas facil.

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

Consulte las funciones avanzadas de XMLHttpRequest .

Admite todos los * navegadores modernos. Y puedo confirmar que he estado usando este enfoque desde que se creó XMLHttpRequest 2. Nunca tuve ningún tipo de problema en ninguno de los navegadores que utilicé.

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

El uso del XMLHttpRequestnombre de la variable es otro gran error, ya que necesita ejecutar la devolución de llamada dentro de los cierres de onload / oreadystatechange, o de lo contrario lo perdió.


Ahora, si desea algo más complejo usando 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)
}

Nuevamente ... es una función muy corta, pero GET y POST.

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 establezca algunos valores personalizados:

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

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

Habiendo dicho eso ... ¿por qué no lo hacemos de la manera más fácil?


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

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

Pero para obtener realmente un error, la única forma es escribir una URL incorrecta, en cuyo caso todos los navegadores arrojan un error.

Los controladores de errores pueden ser útiles si establece encabezados personalizados, establece responseType en el búfer de matriz de blob 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()bajo this.statusTextcomo Method not Allowed.

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

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

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

Solo existe el this.typeque está configurado como error .

¿Por qué agregar un controlador de errores si no tiene ningún 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().

Por lo tanto: no es necesario realizar comprobaciones de errores si puede copiar y pegar la URL correctamente. ;)

PD: Como la primera prueba escribí x ('x', displayAjax) ..., y obtuvo una respuesta total ... ??? 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 lol'd


Leer un archivo sincrónico

No hagas eso.

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

Otro punto es ... si trabaja con API o solo con los archivos de su propia lista o lo que sea, siempre usa diferentes funciones para cada solicitud ...

Solo si tienes una página donde cargas siempre el mismo XML / JSON o lo que sea, solo necesitas 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 ampliar 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, solo con GET ...

Pero puedes hacer muchas cosas con XMLHttpRequest 2:

Hice un administrador de descargas (usando rangos en ambos lados con currículum, lector de archivos y sistema de archivos), varios convertidores de tamaño de imagen usando lienzo, poblado de bases de datos web SQL con imágenes base64 y mucho más ...

Pero en estos casos, debe crear una función solo para ese propósito ... a veces necesita un blob, búferes de matriz, puede establecer encabezados, anular el tipo mime y hay mucho más ...

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

3
  • 19
    Si bien esta respuesta es agradable (y a todos nos encanta XHR2 y publicar datos de archivos y datos de varias partes es totalmente increíble), esto muestra el azúcar sintáctico para publicar XHR con JavaScript, es posible que desee poner esto en una publicación de blog (me gustaría) o incluso en una biblioteca (no estoy seguro del nombre x, ajaxo xhrpodría ser mejor :)). No veo cómo aborda la devolución de la respuesta de una llamada AJAX. (alguien aún podría hacerlo var res = x("url")y no entender por qué no funciona;)). En una nota al margen: sería genial si regresara cdel método para que los usuarios puedan conectarse, erroretc. 23 de agosto de 2013 a las 5:56
  • 29
    2.ajax is meant to be async.. so NO var res=x('url').. Ese es el objetivo de esta pregunta y respuestas :) 23/0813 a las 17:28
  • dieciséis
    @cocco ¿Entonces escribiste un código engañoso e ilegible en una respuesta SO para ahorrar algunas pulsaciones de teclas? Por favor, no hagas eso.
    stone
    8/10/2017 a las 6:20
343

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

Esto significa AngularJS, jQuery (con diferido), reemplazo de XHR nativo (recuperación), Ember.js , guardado de Backbone.js o cualquier biblioteca de Node.js que devuelva promesas.

Su 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 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 promesas, ya sea en el frontend o en el backend.


El problema central

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

Cada vez que llama a un método que devuelve una promesa, los thencontroladores siempre se ejecutan de forma asincrónica, es decir, después del código debajo de ellos que no está en un .thencontrolador.

Esto significa que cuando devuelve datael thencontrolador que ha definido, aún no se ha ejecutado. 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 dataes undefinedporque la data = 5pieza 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 aún no se realizó (Ajax, llamada al servidor, E / S y temporizador), está devolviendo el valor antes de que la solicitud tenga la oportunidad de decirle a su código cuál es ese valor.

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

Resumen rápido de promesas

Una promesa es un valor a lo largo del tiempo . Las promesas tienen estado. Empiezan 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. Puede adjuntar thencontroladores a las promesas para extraer su valor y manejar errores. thenlos manejadores permiten el encadenamiento de llamadas. Las promesas se crean mediante el uso de API que las devuelven . Por ejemplo, el reemplazo de Ajax más moderno fetcho las $.getpromesas de devolución de jQuery .

Cuando invocamos .thenuna promesa y devolvemos algo de ella, obtenemos una promesa por el valor procesado . Si devolvemos otra promesa, obtendremos cosas increíbles, pero sujetemos 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 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 thenpara 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, and 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 se puede hacer debido a la modelo de concurrencia - estamos devolviendo un envoltorio para 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, 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 suele ser útil para secuencias, por ejemplo:

function* foo(){ // Notice the star. This is ES6, so new browsers, Nodes.js, and io.js 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 espacio a 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 valores futuros , es decir: promesas.

Este es un truco algo complicado, pero muy poderoso, que nos permite escribir código asíncrono de manera síncrona. Hay varios "corredores" que hacen esto por ti. Escribir uno son unas pocas líneas de código, pero está más allá del alcance de esta respuesta. Usaré Bluebird's Promise.coroutineaquí, pero hay otras envolturas como coo Q.async.

var foo = coroutine(function*(){
    var data = yield fetch("/echo/json"); // Notice the yield
    // The 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 corrutinas. Por ejemplo:

var main = coroutine(function*(){
   var bar = yield foo(); // Wait our earlier coroutine. It returns a promise
   // The server call is done here, and the 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 are done
});
main();

ES2016 (ES7)

En ES7, esto se estandariza aún más. Hay varias propuestas ahora mismo, pero en todas ellas puedes awaitprometer. Esto es simplemente "azúcar" (sintaxis más agradable) para la propuesta ES6 anterior al agregar las palabras clave asyncy await. 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
}

Todavía devuelve una promesa de todos modos :)

1
  • 1
    Me gusta esto, porque sugieres return fetch, que ya usa promesas.
    RiZKiT
    7 de octubre a las 12:35
270

Estás usando Ajax incorrectamente. La idea no es que devuelva nada, sino que entregue 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.

1
  • dieciséis
    Esta respuesta es completamente semántica ... su método de éxito es solo una devolución de llamada dentro de una devolución de llamada. Podrías haberlo hecho success: handleDatay funcionaría. 4 de ene. De 2016 a las 15:49
256

Responderé con un cómic dibujado a mano de aspecto horrible. La segunda imagen es la razón por la que resultestá undefineden su ejemplo de código.

ingrese la descripción de la imagen aquí

7
  • 42
    Una imagen vale más que mil palabras , Persona A - Pregunte a la persona B detalles para arreglar su automóvil, a su vez Persona B - Hace una llamada Ajax y espera la respuesta del servidor para obtener detalles de reparación del automóvil, cuando se recibe la respuesta, la función Ajax Success llama a la Persona B y le pasa la respuesta como argumento, la Persona A recibe la respuesta. 31/10/2016 a las 17:48
  • 18
    Sería genial si agregaras líneas de código con cada imagen para ilustrar los conceptos. 5 de febrero de 2018 a las 0:32
  • 7
    Mientras tanto, el tipo del coche está atascado a un lado de la carretera. Se requiere el coche se fija antes de continuar. Ahora está solo al lado de la carretera esperando ... Preferiría estar al teléfono esperando cambios de estado, pero el mecánico no lo haría ... El mecánico dijo que tiene que seguir con su trabajo y no puede simplemente pasa el rato en el teléfono. El mecánico prometió que lo llamaría tan pronto como pudiera. Después de aproximadamente 4 horas, el tipo se da por vencido y llama a Uber. - Ejemplo de tiempo de espera. 22/10/19 a las 23:42
  • Ja, ja, muy buen trabajo, @JohannesFahrenkrug! Será muy útil para los entrenadores que intentan enseñar async a principiantes.
    Prime
    5 de marzo a las 2:41
  • 1
    @FingLixon No es un cómic perfecto en absoluto :-D. La segunda imagen debería ilustrar lo que sucede cuando intenta leer un valor demasiado pronto (antes de que se produzca una devolución de llamada). La tercera imagen ilustra la configuración de un método de devolución de llamada: El tipo de la izquierda es básicamente el controlador de devolución de llamada: se le llamará con la información una vez que esté disponible y luego podrá hacer con ella lo que quiera. Ahora creo que fue una mala idea tener DOS llamadas telefónicas en este cómic: la llamada a la tienda y la llamada al tipo de la izquierda. Debería haberlo simplificado, lo siento. 12 abr a las 20:44
251

La solución más simple es crear una función JavaScript y llamarla para la successdevolución de llamada Ajax .

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 a 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);
});
4
  • 9
    No sé quién lo votó en contra. Pero esta es una solución que ha funcionado, de hecho, utilicé este enfoque para crear una aplicación completa. El jquery.ajax no devuelve datos, por lo que es mejor utilizar el enfoque anterior. Si está mal, explique y sugiera una mejor manera de hacerlo. 28 de marzo de 2014 a las 18:12
  • 18
    Lo siento, olvidé dejar un comentario (¡normalmente lo hago!). Lo voté en contra. Los votos negativos no indican la exactitud de los hechos o la falta de ellos, indican utilidad en el contexto o falta de. No encuentro su respuesta útil dada la de Felix, que ya explica esto solo con mucho más detalle. En una nota al margen, ¿por qué especificarías la respuesta si es JSON? 10/04/2014 a las 9:18
  • 9
    ok .. @Benjamin utilicé stringify, para convertir un objeto JSON en una cadena. Y gracias por aclarar tu punto. Tendremos en cuenta para publicar respuestas más elaboradas. 10/04/2014 a las 10:27
  • ¿Y si quieres devolver el "responseObj" fuera de "successCallback" ... :) ... cómo lo harás ...? ... porque un simple retorno lo devolverá a la devolución de llamada "success" del ajax ... y no fuera de "successCallback" ... 19/02/2016 a las 16:02
173

Angular 1

Las personas que usan AngularJS pueden manejar esta situación usando promesas .

Aquí dice

Promises can be used to unnest asynchronous functions and allows one to chain multiple functions together.

También puede encontrar una buena explicación aquí .

Un ejemplo que se encuentra en la documentación que se menciona a continuación.

  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.

Angular 2 y posterior

En Angular 2, mire el siguiente ejemplo, pero se recomienda usar observables con Angular 2.

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

Vea la publicación original aquí. Pero TypeScript no admite promesas nativas de ES6 , si desea usarlo, es posible que necesite un complemento para eso.

Además, aquí está la especificación de promesas .

2
  • 18
    Sin embargo, esto no explica cómo las promesas resolverían este problema. 4 de noviembre de 2014 a las 2:29
  • 7
    Los métodos jQuery y fetch también devuelven promesas. Sugeriría revisar tu respuesta. Aunque jQuery's no es lo mismo (entonces está ahí, pero no lo es catch). 19/02/15 a las 19:24
171

La mayoría de las respuestas aquí brindan sugerencias útiles para cuando tiene una sola operación asíncrona, pero a veces, esto surge cuando necesita hacer una operación asíncrona para cada entrada en una matriz u otra estructura similar a una 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 no funciona es que las devoluciones de llamada de doSomethingAsyncaún no se han ejecutado 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: Hacer las operaciones en paralelo (superpuestas) o en serie (una tras otra en secuencia).

Paralelo

Puede iniciarlos todos y realizar un seguimiento de la cantidad de devoluciones de llamada que espera, y luego usar los resultados cuando haya recibido 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:", JSON.stringify(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 prescindir expectingy solo usar results.length === theArray.length, pero eso nos deja abiertos a la posibilidad de que theArrayse cambie mientras las llamadas estén pendientes ...)

Observe cómo usamos indexfrom forEachpara guardar el resultado en resultsla misma posición que la entrada con la que se relaciona, incluso si los resultados llegan fuera de orden (ya que las llamadas asíncronas no necesariamente se completan en el orden en que se iniciaron).

Pero, ¿qué sucede si necesita devolver esos resultados de una función? Como han señalado las otras respuestas, no puede; tiene que hacer que su función acepte y llame a una devolución de llamada (o devuelva una 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:", JSON.stringify(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í hay una versión que devuelve un Promiseen su 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 doSomethingAsyncnos pasaran errores, solíamos rejectrechazar la promesa cuando recibíamos 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:", JSON.stringify(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, puede hacer un envoltorio para doSomethingAsyncque devuelva una promesa y luego haga lo siguiente ...)

Si doSomethingAsyncte da una Promesa , puedes usar Promise.all:

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

Si sabe que doSomethingAsyncignorará un segundo y tercer argumento, puede simplemente pasárselo directamente a map( mapllama a su devolución de llamada con tres argumentos, pero la mayoría de las personas solo usan el primero la mayor parte del tiempo):

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

Ejemplo:

function doSomethingWith(theArray) {
    return Promise.all(theArray.map(doSomethingAsync));
}
doSomethingWith([1, 2, 3]).then(function(results) {
    console.log("Results:", JSON.stringify(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.allresuelve su promesa con una matriz de los resultados de todas las promesas que le da cuando están todas resueltas, o rechaza su promesa cuando la primera de las promesas que le da la rechaza.

Serie

¿Supongamos que no desea que las operaciones se realicen 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 usar, results.push(result)ya que sabemos que no obtendremos resultados desordenados. En lo anterior podríamos haber usado results[index] = result;, pero en algunos de los siguientes ejemplos no tenemos un índice 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:", JSON.stringify(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, nuevamente, construya un contenedor para doSomethingAsynceso le da una promesa y haga lo siguiente ...)

Si doSomethingAsyncte da una Promesa, si puedes usar la sintaxis ES2017 + (quizás con un transpiler como Babel ), puedes usar una asyncfunción con for-ofy 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:", JSON.stringify(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 + (todavía), puede usar una variación del patrón "Promise reduce" (esto es más complejo que el Promise reduce habitual porque no estamos pasando el resultado de uno a otro, sino reuniendo 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:", JSON.stringify(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 ES2015 + funciones de flecha :

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:", JSON.stringify(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; }
4
  • 4
    ¿Podría explicar cómo funciona la if (--expecting === 0)parte del código, por favor? La versión de devolución de llamada de su solución está funcionando muy bien para mí, simplemente no entiendo cómo, con esa declaración, está verificando la cantidad de respuestas completadas. Aprecio que es solo falta de conocimiento de mi parte. ¿Existe una forma alternativa de emitir un cheque?
    Sarah
    28 de mayo de 2017 a las 10:21
  • 4
    @Sarah: expectingcomienza con el valor de array.length, que es la cantidad de solicitudes que vamos a realizar. Sabemos que no se llamará a la devolución de llamada hasta que se inicien todas esas solicitudes. En la devolución de llamada, if (--expecting === 0)hace lo siguiente: 1. Disminuciones expecting(hemos recibido una respuesta, por lo que esperamos una respuesta menos) y si el valor después de la disminución es 0 (no esperamos más respuestas), estamos ¡hecho! 28 de mayo de 2017 a las 18:31
  • 1
    @Henke: creo que es una preferencia personal, y aunque normalmente prefiero registrar datos sin procesar y dejar que la consola lo maneje, en este caso específico, creo que tiene razón sobre el cambio. ¡Gracias! :-) 6 de mayo a las 11:27
  • 1
    Por conveniencia para mí (¿y para otros?), Agrego un enlace a una respuesta relacionada: Cómo hacer muchas llamadas asincrónicas y esperarlas todas .
    Henke
    1 de junio a las 14:01
120

Eche un vistazo a este ejemplo:

var app = angular.module('plunker', []);

app.controller('MainCtrl', function($scope,$http) {

    var getJoke = function(){
        return $http.get('http://api.icndb.com/jokes/random').then(function(res){
            return res.data.value;  
        });
    }

    getJoke().then(function(res) {
        console.log(res.joke);
    });
});

Como puede ver, getJokeestá devolviendo una promesa resuelta (se resuelve al regresar res.data.value). Entonces, espere hasta que se complete la solicitud $ http.get y luego se ejecute console.log (res.joke) (como un flujo asíncrono normal).

Este es el plnkr:

http://embed.plnkr.co/XlNR7HpCaIhJxskMJfSg/

ES6 way (async - await)

(function(){
  async function getJoke(){
    let response = await fetch('http://api.icndb.com/jokes/random');
    let data = await response.json();
    return data.value;
  }

  getJoke().then((joke) => {
    console.log(joke);
  });
})();
0
119

Este es uno de los lugares en los que el enlace de datos bidireccional o el concepto de almacenamiento que se utiliza en muchos nuevos marcos de JavaScript funcionarán muy bien para usted ...

Entonces, si está utilizando Angular , React o cualquier otro marco que realice un enlace de datos bidireccional o un concepto de almacenamiento, este problema simplemente se solucionó para usted, por lo que, en palabras sencillas, su resultado está undefineden la primera etapa, por lo que tiene result = undefinedantes recibe los datos, luego, tan pronto como obtenga el resultado, se actualizará y se le asignará el nuevo valor cuya respuesta de su llamada Ajax ...

Pero, ¿cómo puede hacerlo en JavaScript puro o jQuery, por ejemplo, como lo hizo en esta pregunta?

Puede usar una devolución de llamada, una promesa y un observable recientemente para manejarlo por usted. Por ejemplo, en las promesas tenemos alguna función como success()o then()que se ejecutará cuando sus datos estén listos para usted. Lo mismo con la devolución de llamada o la función de suscripción en un observable.

Por ejemplo, en su caso en el que está utilizando jQuery, puede hacer algo como esto:

$(document).ready(function(){
    function foo() {
        $.ajax({url: "api/data", success: function(data){
            fooDone(data); // After we have data, we pass it to fooDone
        }});
    };

    function fooDone(data) {
        console.log(data); // fooDone has the data and console.log it
    };

    foo(); // The call happens here
});

Para obtener más información, estudie las promesas y los observables, que son formas más nuevas de hacer estas cosas asincrónicas.

3
  • 2
    Esto está bien en el ámbito global, pero en algún contexto de módulo probablemente desee garantizar el contexto correcto para la devolución de llamada, por ejemplo $.ajax({url: "api/data", success: fooDone.bind(this)}); 24 de julio de 2017 a las 6:14
  • 12
    Esto es realmente incorrecto ya que React es un enlace de datos unidireccional 4 de mayo de 2018 a las 15:57
  • 2
    @MatthewBrent no estás equivocado, pero tampoco tienes razón, los accesorios de React son un objeto y si se cambian, cambian en toda la aplicación, pero no es una forma que el desarrollador de React recomiende usar ...
    Alireza
    14 de mayo de 2018 a las 7:34
116

Es un problema muy común que enfrentamos mientras luchamos con los 'misterios' de JavaScript. Déjame intentar desmitificar este misterio hoy.

Comencemos con una función simple de JavaScript:

function foo(){
    // Do something
    return 'wohoo';
}

let bar = foo(); // 'bar' is 'wohoo' here

Esa es una simple llamada de función síncrona (donde cada línea de código 'termina con su trabajo' antes de la siguiente en secuencia), y el resultado es el mismo que el esperado.

Ahora agreguemos un poco de giro, introduciendo un pequeño retraso en nuestra función, de modo que todas las líneas de código no estén "terminadas" en secuencia. Así, emulará el comportamiento asincrónico de la función:

function foo(){
    setTimeout( ()=> {
        return 'wohoo';
   }, 1000)
}

let bar = foo() // 'bar' is undefined here

Ahí vas; ¡Ese retraso acaba de romper la funcionalidad que esperábamos! Pero, ¿qué pasó exactamente? Bueno, en realidad es bastante lógico si miras el código.

La función foo(), al ejecutarse, no devuelve nada (por lo tanto, el valor devuelto es undefined), pero inicia un temporizador, que ejecuta una función después de 1 segundo para devolver 'wohoo'. Pero como puede ver, el valor que se asigna a bar es el material devuelto inmediatamente por foo (), que no es nada, es decir, solo undefined.

Entonces, ¿cómo abordamos este problema?

Pidamos una promesa a nuestra función . Promise se trata realmente de lo que significa: significa que la función le garantiza que proporcionará cualquier resultado que obtenga en el futuro. Así que veámoslo en acción para nuestro pequeño problema anterior:

function foo(){
   return new Promise((resolve, reject) => { // I want foo() to PROMISE me something
    setTimeout ( function(){
      // Promise is RESOLVED, when the execution reaches this line of code
       resolve('wohoo') // After 1 second, RESOLVE the promise with value 'wohoo'
    }, 1000 )
  })
}

let bar;
foo().then( res => {
    bar = res;
    console.log(bar) // Will print 'wohoo'
});

Por lo tanto, el resumen es: para abordar las funciones asincrónicas como las llamadas basadas en Ajax, etc., puede usar una promesa para resolveel valor (que tiene la intención de devolver). Así, en resumen, resuelves valor en lugar de devolver , en funciones asincrónicas.

ACTUALIZAR (promesas con async / await)

Además de usar then/catchpara trabajar con promesas, existe un enfoque más. La idea es reconocer una función asincrónica y luego esperar a que se resuelvan las promesas , antes de pasar a la siguiente línea de código. Sigue siendo solo algo promisesoculto, pero con un enfoque sintáctico diferente. Para aclarar las cosas, puede encontrar una comparación a continuación:

luego / captura la versión:

function saveUsers(){
     getUsers()
      .then(users => {
         saveSomewhere(users);
      })
      .catch(err => {
          console.error(err);
       })
 }

versión async / await:

  async function saveUsers(){
     try{
        let users = await getUsers()
        saveSomewhere(users);
     }
     catch(err){
        console.error(err);
     }
  }
2
  • ¿Se sigue considerando esta la mejor manera de devolver un valor de una promesa o asincrónica / espera? 26 de septiembre de 2018 a las 20:58
  • 7
    @edwardsmarkf Personalmente, no creo que haya una mejor manera de hacerlo. Utilizo promesas con then / catch, async / await, así como generadores para partes asincrónicas de mi código. Depende en gran medida del contexto de uso. 3 oct 2018 a las 16:12
108

Otro enfoque para devolver un valor de una función asincrónica es pasar un objeto que almacenará el resultado de la función asincrónica.

Aquí hay un ejemplo de lo mismo:

var async = require("async");

// This wires up result back to the caller
var result = {};
var asyncTasks = [];
asyncTasks.push(function(_callback){
    // some asynchronous operation
    $.ajax({
        url: '...',
        success: function(response) {
            result.response = response;
            _callback();
        }
    });
});

async.parallel(asyncTasks, function(){
    // result is available after performing asynchronous operation
    console.log(result)
    console.log('Done');
});

Estoy usando el resultobjeto para almacenar el valor durante la operación asincrónica. Esto permite que el resultado esté disponible incluso después del trabajo asincrónico.

Utilizo mucho este enfoque. Me interesaría saber qué tan bien funciona este enfoque cuando está involucrado el cableado del resultado a través de módulos consecutivos.

1
  • 12
    No hay nada especial en usar un objeto aquí. También funcionaría si asignaras la respuesta directamente a result. Funciona porque está leyendo la variable después de que se completa la función asíncrona. 2 de septiembre de 2015 a las 13:18
95

Si bien las promesas y las devoluciones de llamada funcionan bien en muchas situaciones, es un fastidio expresar algo como:

if (!name) {
  name = async1();
}
async2(name);

Terminarías atravesando async1; compruebe si nameno está definido o no y llame a la devolución de llamada en consecuencia.

async1(name, callback) {
  if (name)
    callback(name)
  else {
    doSomething(callback)
  }
}

async1(name, async2)

Si bien está bien en pequeños ejemplos, se vuelve molesto cuando tiene muchos casos similares y manejo de errores involucrados.

Fibers ayuda a resolver el problema.

var Fiber = require('fibers')

function async1(container) {
  var current = Fiber.current
  var result
  doSomething(function(name) {
    result = name
    fiber.run()
  })
  Fiber.yield()
  return result
}

Fiber(function() {
  var name
  if (!name) {
    name = async1()
  }
  async2(name)
  // Make any number of async calls from here
}

Puedes consultar el proyecto aquí .

4
  • 3
    @recurf - No es mi proyecto. Puede intentar usar su rastreador de problemas. 20 de enero de 2017 a las 15:22
  • 3
    ¿Es esto similar a las funciones del generador? developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/… * 7 de junio de 2017 a las 3:19
  • 3
    ¿Sigue siendo relevante? 18/03/18 a las 19:43
  • 2
    Puede utilizarlo async-awaitsi está utilizando algunas de las versiones más recientes de node. Si alguien se queda atascado con versiones anteriores, puede usar este método. 20 de marzo de 2018 a las 8:18
92

El siguiente ejemplo que he escrito muestra cómo

  • Manejar llamadas HTTP asincrónicas;
  • Espere la respuesta de cada llamada a la API;
  • Utilice el patrón Promise ;
  • Utilice el patrón Promise.all para unirse a múltiples llamadas HTTP;

Este ejemplo de trabajo es autónomo. Definirá un objeto de solicitud simple que usa el XMLHttpRequestobjeto de ventana para realizar llamadas. Definirá una función simple para esperar a que se completen un montón de promesas.

Contexto. El ejemplo es consultar el punto final de la API web de Spotify para buscar playlistobjetos para un conjunto determinado de cadenas de consulta:

[
 "search?type=playlist&q=%22doom%20metal%22",
 "search?type=playlist&q=Adele"
]

Para cada elemento, una nueva Promesa activará un bloque ExecutionBlock, analizará el resultado, programará un nuevo conjunto de promesas en función de la matriz de resultados, es decir, una lista de userobjetos de Spotify y ejecutará la nueva llamada HTTP dentro de la ExecutionProfileBlockforma asincrónica.

Luego puede ver una estructura de Promise anidada, que le permite generar múltiples llamadas HTTP anidadas completamente asincrónicas, y unir los resultados de cada subconjunto de llamadas Promise.all.

NOTA Las searchAPI de Spotify recientes requerirán que se especifique un token de acceso en los encabezados de la solicitud:

-H "Authorization: Bearer {your access token}" 

Entonces, para ejecutar el siguiente ejemplo, debe colocar su token de acceso en los encabezados de la solicitud:

var spotifyAccessToken = "YourSpotifyAccessToken";
var console = {
    log: function(s) {
        document.getElementById("console").innerHTML += s + "<br/>"
    }
}

// Simple XMLHttpRequest
// based on https://davidwalsh.name/xmlhttprequest
SimpleRequest = {
    call: function(what, response) {
        var request;
        if (window.XMLHttpRequest) { // Mozilla, Safari, ...
            request = new XMLHttpRequest();
        } else if (window.ActiveXObject) { // Internet Explorer
            try {
                request = new ActiveXObject('Msxml2.XMLHTTP');
            }
            catch (e) {
                try {
                  request = new ActiveXObject('Microsoft.XMLHTTP');
                } catch (e) {}
            }
        }

        // State changes
        request.onreadystatechange = function() {
            if (request.readyState === 4) { // Done
                if (request.status === 200) { // Complete
                    response(request.responseText)
                }
                else
                    response();
            }
        }
        request.open('GET', what, true);
        request.setRequestHeader("Authorization", "Bearer " + spotifyAccessToken);
        request.send(null);
    }
}

//PromiseAll
var promiseAll = function(items, block, done, fail) {
    var self = this;
    var promises = [],
                   index = 0;
    items.forEach(function(item) {
        promises.push(function(item, i) {
            return new Promise(function(resolve, reject) {
                if (block) {
                    block.apply(this, [item, index, resolve, reject]);
                }
            });
        }(item, ++index))
    });
    Promise.all(promises).then(function AcceptHandler(results) {
        if (done) done(results);
    }, function ErrorHandler(error) {
        if (fail) fail(error);
    });
}; //promiseAll

// LP: deferred execution block
var ExecutionBlock = function(item, index, resolve, reject) {
    var url = "https://api.spotify.com/v1/"
    url += item;
    console.log( url )
    SimpleRequest.call(url, function(result) {
        if (result) {

            var profileUrls = JSON.parse(result).playlists.items.map(function(item, index) {
                return item.owner.href;
            })
            resolve(profileUrls);
        }
        else {
            reject(new Error("call error"));
        }
    })
}

arr = [
    "search?type=playlist&q=%22doom%20metal%22",
    "search?type=playlist&q=Adele"
]

promiseAll(arr, function(item, index, resolve, reject) {
    console.log("Making request [" + index + "]")
    ExecutionBlock(item, index, resolve, reject);
}, function(results) { // Aggregated results

    console.log("All profiles received " + results.length);
    //console.log(JSON.stringify(results[0], null, 2));

    ///// promiseall again

    var ExecutionProfileBlock = function(item, index, resolve, reject) {
        SimpleRequest.call(item, function(result) {
            if (result) {
                var obj = JSON.parse(result);
                resolve({
                    name: obj.display_name,
                    followers: obj.followers.total,
                    url: obj.href
                });
            } //result
        })
    } //ExecutionProfileBlock

    promiseAll(results[0], function(item, index, resolve, reject) {
        //console.log("Making request [" + index + "] " + item)
        ExecutionProfileBlock(item, index, resolve, reject);
    }, function(results) { // aggregated results
        console.log("All response received " + results.length);
        console.log(JSON.stringify(results, null, 2));
    }

    , function(error) { // Error
        console.log(error);
    })

    /////

  },
  function(error) { // Error
      console.log(error);
  });
<div id="console" />

He discutido ampliamente esta solución aquí .

91

La respuesta corta es que debe implementar una devolución de llamada como esta:

function callback(response) {
    // Here you can do what ever you want with the response object.
    console.log(response);
}

$.ajax({
    url: "...",
    success: callback
});
0
86

JavaScript is single threaded.

El navegador se puede dividir en tres partes:

  1. Bucle de eventos

  2. API web

  3. Cola de eventos

El ciclo de eventos se ejecuta para siempre, es decir, una especie de ciclo infinito. La cola de eventos es donde todas sus funciones se insertan en algún evento (ejemplo: clic).

Esto se lleva a cabo uno a uno fuera de la cola y se coloca en el bucle de eventos que ejecuta esta función y se prepara para el siguiente después de que se ejecute el primero. Esto significa que la ejecución de una función no comienza hasta que la función anterior a ella en la cola se ejecuta en el bucle de eventos.

Ahora pensemos que empujamos dos funciones en una cola. Uno es para obtener datos del servidor y otro utiliza esos datos. Primero pusimos la función serverRequest () en la cola y luego la función utiliseData (). La función serverRequest entra en el bucle de eventos y realiza una llamada al servidor, ya que nunca sabemos cuánto tiempo llevará obtener los datos del servidor, por lo que se espera que este proceso lleve tiempo y, por lo tanto, ocupamos nuestro bucle de eventos y colgamos nuestra página.

Ahí es donde entra en juego la API web. Toma esta función del bucle de eventos y se ocupa de que el servidor libere el bucle de eventos, de modo que podamos ejecutar la siguiente función de la cola.

La siguiente función en la cola es utiliseData () que va en el bucle, pero debido a que no hay datos disponibles, se desperdicia y la ejecución de la siguiente función continúa hasta el final de la cola. (Esto se llama llamada asíncrona, es decir, podemos hacer otra cosa hasta que obtengamos datos).

Supongamos que nuestra función serverRequest () tiene una declaración de retorno en código. Cuando recuperamos datos de la API web del servidor, los colocará en la cola al final de la cola.

A medida que se empuja al final de la cola, no podemos utilizar sus datos ya que no queda ninguna función en nuestra cola para utilizar estos datos. Por lo tanto, no es posible devolver algo de la llamada asincrónica.

Por lo tanto, la solución a esto es devolución de llamada o promesa .

Damos nuestra función (función que utiliza datos devueltos por el servidor) a una función que llama al servidor.

Llamar de vuelta

function doAjax(callbackFunc, method, url) {
    var xmlHttpReq = new XMLHttpRequest();
    xmlHttpReq.open(method, url);
    xmlHttpReq.onreadystatechange = function() {

        if (xmlHttpReq.readyState == 4 && xmlHttpReq.status == 200) {
            callbackFunc(xmlHttpReq.responseText);
        }
    }
    xmlHttpReq.send(null);
}

En mi código se llama como:

function loadMyJson(categoryValue){
    if(categoryValue === "veg")
        doAjax(print, "GET", "http://localhost:3004/vegetables");
    else if(categoryValue === "fruits")
        doAjax(print, "GET", "http://localhost:3004/fruits");
    else
      console.log("Data not found");
}

Devolución de llamada de JavaScript.info

0
84

Respuesta de 2017: ahora puede hacer exactamente lo que quiera en todos los navegadores actuales y Node.js

Esto es bastante simple:

  • Devolver una promesa
  • Use 'await' , que le dirá a JavaScript que espere la promesa para que se resuelva en un valor (como la respuesta HTTP)
  • Agregue la palabra clave 'async' a la función principal

Aquí tienes una versión funcional de tu código:

(async function(){

    var response = await superagent.get('...')
    console.log(response)

})()

await es compatible con todos los navegadores actuales y Node.js 8

4
  • 9
    Desafortunadamente, esto solo funciona con funciones que devuelven promesas; por ejemplo, no funciona con la API Node.js, que usa devoluciones de llamada. Y no recomendaría usarlo sin Babel, porque no todo el mundo usa "navegadores actuales". 8 de junio de 2017 a las 6:47
  • 4
    El nodo 8 de @ MichałPerłakowski incluye nodejs.org/api/util.html#util_util_promisify_original que se puede usar para hacer que la API de node.js devuelva promesas. Si tiene el tiempo y el dinero para admitir navegadores no actuales, obviamente depende de su situación. 9 de junio de 2017 a las 18:28
  • IE 11 sigue siendo un navegador actual en 2018, lamentablemente y no es compatible await/async 4 oct 2018 a las 14:51
  • IE11 no es un navegador actual. Fue lanzado hace 5 años, tiene una participación de mercado mundial del 2.5% según caniuse, y a menos que alguien esté duplicando su presupuesto para ignorar toda la tecnología actual, entonces no vale la pena el tiempo de la mayoría de la gente. 4 oct 2018 a las 14:57
71

Puede utilizar esta biblioteca personalizada (escrita con Promise) para realizar una llamada remota.

function $http(apiConfig) {
    return new Promise(function (resolve, reject) {
        var client = new XMLHttpRequest();
        client.open(apiConfig.method, apiConfig.url);
        client.send();
        client.onload = function () {
            if (this.status >= 200 && this.status < 300) {
                // Performs the function "resolve" when this.status is equal to 2xx.
                // Your logic here.
                resolve(this.response);
            }
            else {
                // Performs the function "reject" when this.status is different than 2xx.
                reject(this.statusText);
            }
        };
        client.onerror = function () {
            reject(this.statusText);
        };
    });
}

Ejemplo de uso simple:

$http({
    method: 'get',
    url: 'google.com'
}).then(function(response) {
    console.log(response);
}, function(error) {
    console.log(error)
});
70

Otra solución es ejecutar código a través del ejecutor secuencial nsynjs .

Si se promete la función subyacente

nsynjs evaluará todas las promesas secuencialmente y pondrá el resultado de la promesa en la datapropiedad:

function synchronousCode() {

    var getURL = function(url) {
        return window.fetch(url).data.text().data;
    };
    
    var url = 'https://ajax.googleapis.com/ajax/libs/jquery/2.0.0/jquery.min.js';
    console.log('received bytes:',getURL(url).length);
    
};

nsynjs.run(synchronousCode,{},function(){
    console.log('synchronousCode done');
});
<script src="https://rawgit.com/amaksr/nsynjs/master/nsynjs.js"></script>

Si la función subyacente no está prometida

Paso 1. Envuelva la función con una devolución de llamada en el contenedor compatible con nsynjs (si tiene una versión prometida, puede omitir este paso):

var ajaxGet = function (ctx,url) {
    var res = {};
    var ex;
    $.ajax(url)
    .done(function (data) {
        res.data = data;
    })
    .fail(function(e) {
        ex = e;
    })
    .always(function() {
        ctx.resume(ex);
    });
    return res;
};
ajaxGet.nsynjsHasCallback = true;

Paso 2. Ponga en funcionamiento la lógica síncrona:

function process() {
    console.log('got data:', ajaxGet(nsynjsCtx, "data/file1.json").data);
}

Paso 3. Ejecute la función de manera síncrona a través de nsynjs:

nsynjs.run(process,this,function () {
    console.log("synchronous function finished");
});

Nsynjs evaluará todos los operadores y expresiones paso a paso, deteniendo la ejecución en caso de que el resultado de alguna función lenta no esté listo.

Más ejemplos están aquí .

1
  • 4
    Esto es interesante. Me gusta cómo permite codificar llamadas asíncronas de la forma en que lo haría en otros idiomas. ¿Pero técnicamente no es JavaScript real? 16 de junio de 2017 a las 23:55
43

ECMAScript 6 tiene 'generadores' que le permiten programar fácilmente en un estilo asincrónico.

function* myGenerator() {
    const callback = yield;
    let [response] = yield $.ajax("https://stackoverflow.com", {complete: callback});
    console.log("response is:", response);

    // examples of other things you can do
    yield setTimeout(callback, 1000);
    console.log("it delayed for 1000ms");
    while (response.statusText === "error") {
        [response] = yield* anotherGenerator();
    }
}

Para ejecutar el código anterior, haga esto:

const gen = myGenerator(); // Create generator
gen.next(); // Start it
gen.next((...args) => gen.next([...args])); // Set its callback function

Si necesita apuntar a navegadores que no son compatibles con ES6, puede ejecutar el código a través de Babel o el compilador de cierre para generar ECMAScript 5.

Las devoluciones de llamada ...argsse envuelven en una matriz y se desestructuran cuando las lee para que el patrón pueda hacer frente a las devoluciones de llamada que tienen múltiples argumentos. Por ejemplo, con el nodo fs :

const [err, data] = yield fs.readFile(filePath, "utf-8", callback);
1
  • ¿Considera que los generadores / generadores asíncronos son solo una solución API asíncrona? ¿O usarías generadores para envolver otra API asincrónica como promesa / diferido? Estoy de acuerdo en que es otra gran adición al universo asincrónico, pero todavía no he encontrado el uso correcto de generadores que me haga adoptarlos. 17 de enero a las 18:52
40

Nos encontramos en un universo que parece progresar a lo largo de una dimensión que llamamos "tiempo". No entendemos realmente qué es el tiempo, pero hemos desarrollado abstracciones y vocabulario que nos permiten razonar y hablar de él: "pasado", "presente", "futuro", "antes", "después".

Los sistemas informáticos que construimos, cada vez más, tienen el tiempo como una dimensión importante. Ciertas cosas están configuradas para suceder en el futuro. Entonces, otras cosas deben suceder después de que esas primeras cosas eventualmente ocurran. Esta es la noción básica llamada "asincronicidad". En nuestro mundo cada vez más interconectado, el caso más común de asincronicidad es esperar a que algún sistema remoto responda a alguna solicitud.

Considere un ejemplo. Llamas al lechero y pides leche. Cuando llegue, querrás ponerlo en tu café. No puedes poner la leche en tu café ahora mismo, porque todavía no está aquí. Tienes que esperar a que venga antes de ponerlo en tu café. En otras palabras, lo siguiente no funcionará:

var milk = order_milk();
put_in_coffee(milk);

Porque JavaScript no tiene forma de saber que necesita esperar a order_milkque termine antes de ejecutarse put_in_coffee. En otras palabras, no sabe que order_milkes asincrónico, es algo que no va a resultar en leche hasta algún tiempo futuro. JavaScript y otros lenguajes declarativos ejecutan una declaración tras otra sin esperar.

El enfoque clásico de JavaScript para este problema, aprovechando el hecho de que JavaScript admite funciones como objetos de primera clase que se pueden pasar, es pasar una función como parámetro a la solicitud asincrónica, que luego invocará cuando se haya completado su tarea en el futuro. Ese es el enfoque de "devolución de llamada". Se parece a esto:

order_milk(put_in_coffee);

order_milkarranca, ordena la leche, luego, cuando y sólo cuando llega, invoca put_in_coffee.

El problema con este enfoque de devolución de llamada es que contamina la semántica normal de una función que informa su resultado con return; en cambio, las funciones no deben informar sus resultados llamando a una devolución de llamada dada como parámetro. Además, este enfoque puede volverse difícil de manejar rápidamente cuando se trata de secuencias de eventos más largas. Por ejemplo, digamos que quiero esperar a que se ponga la leche en el café, y luego y solo entonces realizar un tercer paso, a saber, beber el café. Termino necesitando escribir algo como esto:

order_milk(function(milk) { put_in_coffee(milk, drink_coffee); }

donde paso put_in_coffeetanto a la leche para ponerla como a la acción ( drink_coffee) para ejecutar una vez que se ha puesto la leche. Este código se vuelve difícil de escribir, leer y depurar.

En este caso, podríamos reescribir el código en la pregunta como:

var answer;
$.ajax('/foo.json') . done(function(response) {
  callback(response.data);
});

function callback(data) {
  console.log(data);
}

Ingresa promesas

Esta fue la motivación para la noción de "promesa", que es un tipo particular de valor que representa un resultado futuro o asincrónico de algún tipo. Puede representar algo que ya sucedió, o que va a suceder en el futuro, o puede que nunca suceda. Las promesas tienen un único método, denominado then, al que se le pasa una acción para que se ejecute cuando se haya cumplido el resultado que representa la promesa.

En el caso de nuestra leche y café, diseñamos order_milkdevolver una promesa por la leche que llega, luego especificamos put_in_coffeecomo thenacción, de la siguiente manera:

order_milk() . then(put_in_coffee)

Una ventaja de esto es que podemos unirlos para crear secuencias de sucesos futuros ("encadenamiento"):

order_milk() . then(put_in_coffee) . then(drink_coffee)

Apliquemos las promesas a su problema particular. Envolveremos nuestra lógica de solicitud dentro de una función, que devuelve una promesa:

function get_data() {
  return $.ajax('/foo.json');
}

En realidad, todo lo que hemos hecho es agregar returna la llamada a $.ajax. Esto funciona porque jQuery's $.ajaxya devuelve una especie de promesa. (En la práctica, sin entrar en detalles, preferiríamos ajustar esta llamada para devolver una promesa real, o usar alguna alternativa para $.ajaxhacerlo). Ahora, si queremos cargar el archivo y esperar a que termine y luego haz algo, simplemente podemos decir

get_data() . then(do_something)

por ejemplo,

get_data() .
  then(function(data) { console.log(data); });

Cuando usamos promesas, terminamos pasando muchas funciones then, por lo que a menudo es útil usar las funciones de flecha más compactas de estilo ES6:

get_data() .
  then(data => console.log(data));

La asyncpalabra clave

Pero todavía hay algo vagamente insatisfactorio en tener que escribir código de una manera si es sincrónico y de una manera bastante diferente si es asincrónico. Para sincrónicos, escribimos

a();
b();

pero si aes asincrónico, con promesas tenemos que escribir

a() . then(b);

Arriba, dijimos, "JavaScript no tiene forma de saber que necesita esperar a que termine la primera llamada antes de ejecutar la segunda". ¿No sería agradable si era alguna forma de saber JavaScript que? Resulta que existe una awaitpalabra clave que se usa dentro de un tipo especial de función llamada función "asíncrona". Esta función es parte de la próxima versión de ECMAScript (ES), pero ya está disponible en transpiladores como Babel con los ajustes preestablecidos adecuados. Esto nos permite simplemente escribir

async function morning_routine() {
  var milk   = await order_milk();
  var coffee = await put_in_coffee(milk);
  await drink(coffee);
}

En su caso, podría escribir algo como

async function foo() {
  data = await get_data();
  console.log(data);
}
39

Respuesta corta : su foo()método regresa inmediatamente, mientras que la $ajax()llamada se ejecuta de forma asincrónica después de que regresa la función . El problema es entonces cómo o dónde almacenar los resultados recuperados por la llamada asíncrona una vez que regresa.

Se han dado varias soluciones en este hilo. Quizás la forma más fácil es pasar un objeto al foo()método y almacenar los resultados en un miembro de ese objeto después de que se complete la llamada asíncrona.

function foo(result) {
    $.ajax({
        url: '...',
        success: function(response) {
            result.response = response;   // Store the async result
        }
    });
}

var result = { response: null };   // Object to hold the async result
foo(result);                       // Returns before the async completes

Tenga en cuenta que la llamada a foo()todavía no devolverá nada útil. Sin embargo, el resultado de la llamada asíncrona ahora se almacenará en formato result.response.

1
  • 15
    Si bien esto funciona, en realidad no es mejor que asignar a una variable global. 23/09/15 a las 22:53
39

A continuación, se muestran algunos enfoques para trabajar con solicitudes asincrónicas:

  1. Objeto Promesa del navegador
  2. P : una biblioteca de promesas para JavaScript
  3. A + Promises.js
  4. jQuery diferido
  5. API XMLHttpRequest
  6. Uso del concepto de devolución de llamada: como implementación en la primera respuesta

Ejemplo: implementación diferida de jQuery para trabajar con múltiples solicitudes

var App = App || {};

App = {
    getDataFromServer: function(){

      var self = this,
                 deferred = $.Deferred(),
                 requests = [];

      requests.push($.getJSON('request/ajax/url/1'));
      requests.push($.getJSON('request/ajax/url/2'));

      $.when.apply(jQuery, requests).done(function(xhrResponse) {
        return deferred.resolve(xhrResponse.result);
      });
      return deferred;
    },

    init: function(){

        this.getDataFromServer().done(_.bind(function(resp1, resp2) {

           // Do the operations which you wanted to do when you
           // get a response from Ajax, for example, log response.
        }, this));
    }
};
App.init();
1
  • ¿Por qué incluir un fragmento de pila que genere un error?
    Henke
    9 de mayo a las 15:47
38

Utilice una callback()función dentro del foo()éxito. Pruébelo de esta manera. Es simple y fácil de entender.

var lat = "";
var lon = "";

function callback(data) {
    lat = data.lat;
    lon = data.lon;
}

function getLoc() {
    var url = "http://ip-api.com/json"
    $.getJSON(url, function(data) {
        callback(data);
    });
}

getLoc();
30

Usando Promesa

La respuesta más perfecta a esta pregunta está usando Promise.

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

Uso

ajax("GET", "/test", "acrive=1").then(function(result) {
    // Code depending on result
})
.catch(function() {
    // An error occurred
});

Pero espera...!

¡Hay un problema con el uso de promesas!

¿Por qué deberíamos usar nuestra propia Promesa personalizada?

Estuve usando esta solución por un tiempo hasta que descubrí que hay un error en los navegadores antiguos:

Uncaught ReferenceError: Promise is not defined

Así que decidí implementar mi propia clase Promise para ES3 a los compiladores de JavaScript inferiores si no está definida. ¡Simplemente agregue este código antes de su código principal y luego use Promise de manera segura!

if(typeof Promise === "undefined"){
    function _classCallCheck(instance, Constructor) {
        if (!(instance instanceof Constructor)) {
            throw new TypeError("Cannot call a class as a function");
        }
    }
    var Promise = function () {
        function Promise(main) {
            var _this = this;
            _classCallCheck(this, Promise);
            this.value = undefined;
            this.callbacks = [];
            var resolve = function resolve(resolveValue) {
                _this.value = resolveValue;
                _this.triggerCallbacks();
            };
            var reject = function reject(rejectValue) {
                _this.value = rejectValue;
                _this.triggerCallbacks();
            };
            main(resolve, reject);
        }
        Promise.prototype.then = function then(cb) {
            var _this2 = this;
            var next = new Promise(function (resolve) {
                _this2.callbacks.push(function (x) {
                    return resolve(cb(x));
                });
            });
            return next;
        };
        Promise.prototype.catch = function catch_(cb) {
            var _this2 = this;
            var next = new Promise(function (reject) {
                _this2.callbacks.push(function (x) {
                    return reject(cb(x));
                });
            });
            return next;
        };
        Promise.prototype.triggerCallbacks = function triggerCallbacks() {
            var _this3 = this;
            this.callbacks.forEach(function (cb) {
                cb(_this3.value);
            });
        };
        return Promise;
    }();
}
1
  • Creo que también podrías usar una devolución de llamada: D, pero esto es increíble.
    Alaska
    28 de septiembre a las 18:32
29

La pregunta era:

How do I return the response from an asynchronous call?

que se puede interpretar como:

How to make asynchronous code look synchronous?

The solution will be to avoid callbacks, and use a combination of Promises and async/await.

I would like to give an example for an Ajax request.

(Although it can be written in JavaScript, I prefer to write it in Python, and compile it to JavaScript using Transcrypt. It will be clear enough.)

Let’s first enable jQuery usage, to have $ available as S:

__pragma__ ('alias', 'S', '$')

Define a function which returns a Promise, in this case an Ajax call:

def read(url: str):
    deferred = S.Deferred()
    S.ajax({'type': "POST", 'url': url, 'data': { },
        'success': lambda d: deferred.resolve(d),
        'error': lambda e: deferred.reject(e)
    })
    return deferred.promise()

Use the asynchronous code as if it were synchronous:

async def readALot():
    try:
        result1 = await read("url_1")
        result2 = await read("url_2")
    except Exception:
        console.warn("Reading a lot failed")
1
  • Anyone interested in using async / await will likely also want to read this answer (and possibly my comment below it :-).
    – Henke
    May 11 at 12:25
28

Of course there are many approaches like synchronous request, promise, but from my experience I think you should use the callback approach. It's natural to asynchronous behavior of JavaScript.

So, your code snippet can be rewritten to be a little different:

function foo() {
    var result;

    $.ajax({
        url: '...',
        success: function(response) {
            myCallback(response);
        }
    });

    return result;
}

function myCallback(response) {
    // Does something.
}
2
  • 6
    There's nothing inherently asynchronous about callbacks or JavaScript. Mar 18 '18 at 19:48
  • Why keep var result; and return result;? The latter will still always return undefined!
    – Henke
    May 9 at 12:21
27

Rather than throwing code at you, there are two concepts that are key to understanding how JavaScript handles callbacks and asynchronicity (is that even a word?)

The Event Loop and Concurrency Model

There are three things you need to be aware of; The queue; the event loop and the stack

In broad, simplistic terms, the event loop is like the project manager, it is constantly listening for any functions that want to run and communicates between the queue and the stack.

while (queue.waitForMessage()) {
  queue.processNextMessage();
}

Once it receives a message to run something it adds it to the queue. The queue is the list of things that are waiting to execute (like your AJAX request). imagine it like this:

  1. call foo.com/api/bar using foobarFunc
  2. Go perform an infinite loop ... and so on

When one of these messages is going to execute it pops the message from the queue and creates a stack, the stack is everything JavaScript needs to execute to perform the instruction in the message. So in our example it's being told to call foobarFunc

function foobarFunc (var) {
  console.log(anotherFunction(var));
}

So anything that foobarFunc needs to execute (in our case anotherFunction) will get pushed onto the stack. executed, and then forgotten about - the event loop will then move onto the next thing in the queue (or listen for messages)

The key thing here is the order of execution. That is

WHEN is something going to run

When you make a call using AJAX to an external party or run any asynchronous code (a setTimeout for example), JavaScript is dependant upon a response before it can proceed.

The big question is when will it get the response? The answer is we don't know - so the event loop is waiting for that message to say "hey run me". If JavaScript just waited around for that message synchronously your app would freeze and it will suck. So JavaScript carries on executing the next item in the queue whilst waiting for the message to get added back to the queue.

That's why with asynchronous functionality we use things called callbacks. - A function or handler that, when passed into another function, will be executed at a later date. A promise uses callbacks (functions passed to .then() for example) as a way to reason about this asynchronous behaviour in a more linear way. The promise is a way of saying "I promise to return something at some point" and the callback is how we handle that value that is eventually returned. jQuery uses specific callbacks called deffered.done deffered.fail and deffered.always (amongst others). You can see them all here

So what you need to do is pass a function that is promised to execute at some point with data that is passed to it.

Because a callback is not executed immediately but at a later time it's important to pass the reference to the function not it executed. so

function foo(bla) {
  console.log(bla)
}

so most of the time (but not always) you'll pass foo not foo()

Hopefully that will make some sense. When you encounter things like this that seem confusing - i highly recommend reading the documentation fully to at least get an understanding of it. It will make you a much better developer.

3
  • I am struggling to accept "callbacks are kind of like promises". it's like saying "flour is kind of like bread" but it is not. you use flour, water and other incredients, mix them and eventually after a process, bread is the results.
    – Eva Cohen
    Jan 17 at 19:08
  • 1
    This is true - I think I was try to say something that doesnt quite read what I was meaning. A promise in JS evidently represents something different to a callback, however when programming any kind asynchronous functionality you are going to be executing a callback. A promise represents the value but the callback is what we need to do something with that value, at some point in the future, when it returns. Jan 18 at 15:58
  • 1
    A promise is mostly useless (but not always) without a callback to do something with the resolved value Jan 18 at 15:59
27

After reading all the responses here and with my experiences, I would like to resume the detail of callback, promise and async/await for the asynchronous programming in JavaScript.

1) Callback: The fundamental reason for a callback is to run code in response of an event (see the example below). We use callback in JavaScript every time.

const body = document.getElementsByTagName('body')[0];
function callback() {
  console.log('Hello');
}
body.addEventListener('click', callback);

But if you must use many nested callbacks in the example below, it will be fairy terrible for the code refactoring.

asyncCallOne(function callback1() {
  asyncCallTwo(function callback2() {
    asyncCallThree(function callback3() {
        ...
    })
  })
})

2) Promise: a syntax ES6 - Promise resolves the callback hell issue!

const myFirstPromise = new Promise((resolve, reject) => {
  // We call resolve(...) when what we were doing asynchronously was successful, and reject(...) when it failed.
  // In this example, we use setTimeout(...) to simulate async code.
  // In reality, you will probably be using something like XHR request or an HTML5 API.
  setTimeout(() => {
    resolve("Success!")  // Yay! Everything went well!
  }, 250)
})

myFirstPromise
  .then((res) => {
    return res.json();
  })
  .then((data) => {
    console.log(data);
  })
  .catch((e) => {
    console.log(e);
  });

myFirstPromise is a Promise instance that represents the process of async codes. The resolve function signals that the Promise instance has finished. Afterwards, we can call .then() (a chain of .then as you want) and .catch() on the promise instance:

then — Runs a callback you pass to it when the promise has fulfilled.
catch — Runs a callback you pass to it when something went wrong.

3) Async/Await: a new syntax ES6 - Await is basically syntactic sugar for Promise!

La función Async nos proporciona una sintaxis limpia y concisa que nos permite escribir menos código para lograr el mismo resultado que obtendríamos con las promesas. Async / Await tiene un aspecto similar al código síncrono , y el código síncrono es mucho más fácil de leer y escribir. Para detectar errores con Async / Await, podemos usar el bloque try...catch. Aquí, no es necesario escribir una cadena de .then () de sintaxis de Promise.

const getExchangeRate = async () => {
  try {
    const res = await fetch('https://getExchangeRateData');
    const data = await res.json();
    console.log(data);
  } catch (err) {
    console.error(err);
  }
}

getExchangeRate();

Conclusion: These are totally the three syntaxes for asynchronous programming in JavaScript that you should well understand. So if possible, I recommend that you should use "promise" or "async/await" for refactoring your asynchronous codes (mostly for XHR requests) !

1
  • Hola, aunque el contenido de esta respuesta es preciso, realmente no responde a la pregunta de OP (¿cuál es cómo devolver algo de una llamada asincrónica?) 25/08/20 a las 12:02