Tengo una función fooque hace 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 asignar el resultado a una variable local dentro de la función y devolver esa, pero ninguna de esas formas realmente 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 función de jQuery ajax)

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

→ 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 asíncrono . Eso significa que enviar la solicitud (o más bien recibir la respuesta) se elimina del flujo de ejecución normal. En su ejemplo, $.ajaxregresa inmediatamente y la siguiente declaració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 síncrono y el asíncrono sea más clara:

Sincrónico

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

Lo mismo sucede cuando realiza una llamada de función que contiene código "normal":

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

var item = findItem();

// Do something with item
doSomethingElse();

Aunque findItempuede llevar mucho tiempo ejecutarlo, cualquier código que venga después var item = findItem();tiene que esperar hasta que la función devuelva el resultado.

Asincrónico

Vuelves a llamar a tu amigo por el mismo motivo. Pero esta vez le dices que tienes prisa y que debería devolverte la llamada a tu teléfono móvil. Cuelgas, sales de casa y haces lo que tenías planeado hacer. Una vez que tu amigo te devuelve la llamada, estás lidiando con la información que te dio.

Eso es exactamente lo que sucede cuando realiza una solicitud de 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 posterior a la llamada Ajax. Para obtener la respuesta eventualmente, proporciona una función para llamar una vez que se recibió la respuesta, una devolución de llamada (¿nota algo? ¿ Devolver la llamada ?). Cualquier declaración que viene después de esa llamada se ejecuta antes de que se llame la devolución de llamada.


solución(es)

¡Adopte la naturaleza asíncrona de JavaScript! Si bien ciertas operaciones asíncronas proporcionan contrapartes síncronas (también lo hace "Ajax"), generalmente se desaconseja usarlas, especialmente en un contexto de navegador.

¿Por qué es malo lo 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, lo que hará que no responda. Además, existe un límite superior en el tiempo de ejecución de JavaScript y el navegador le preguntará al usuario si desea continuar con la ejecución o no.

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

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

  • Promesas conasync/await (ES2017+, disponible en navegadores más antiguos si usa un transpilador o regenerador)
  • Devoluciones 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 conasync/await

La versión de ECMAScript lanzada en 2017 introdujo compatibilidad a nivel de sintaxis para funciones asíncronas. Con la ayuda de asyncy await, puede escribir de forma asíncrona en un "estilo síncrono". El código sigue siendo asíncrono, 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 se puede usar awaitdentro de una asyncfunción o en un módulo de JavaScript . El nivel superior awaitno se admite fuera de los módulos, por lo que es posible que deba crear un IIFE asíncrono ( Expresión de función invocada inmediatamente ) para iniciar un asynccontexto si no usa un módulo.

Puedes leer más sobre asyncy awaiten 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 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, se llamará a la devolución de llamada cada vez que finalice 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 usarla como successdevolución de llamada. Así que esto

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

se convierte

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

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

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

foo(myCallback);

foomismo 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 a 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 con result, ya que así es como definimos la devolución de llamada).

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

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

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


ES2015+: Promesas con entonces()

Promise API es una característica nueva de ECMAScript 6 (ES2015), pero ya tiene un buen soporte de navegador . 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 asíncronas (por ejemplo, bluebird ).

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

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

Aquí hay 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 ofrecen las promesas está más allá del alcance de esta respuesta, pero si escribe 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 la promesa

Tenga en cuenta que las promesas y los objetos diferidos son solo contenedores de 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 malinterpreta 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" sincrónicas

Como mencioné, algunas (!) operaciones asíncronas tienen contrapartes síncronas. No aconsejo su uso, pero en aras de la exhaustividad, así es como realizaría una llamada síncrona:

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. Luego puede seguir usando 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, $.getJSON, etc., 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, siempre es asíncrono (una razón más para ni siquiera considerar esta opción).

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

Tu código debería ser algo así como esto:

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

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

Felix Kling hizo un buen trabajo al escribir una respuesta para las personas que usan jQuery para AJAX, pero he decidido brindar una alternativa para las personas que no lo hacen.

( Tenga en cuenta que para aquellos que usan la nueva fetchAPI, Angular o promesas, he agregado otra respuesta a continuación )


lo que estás enfrentando

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

La A en AJAX significa asíncrono . Eso significa que enviar la solicitud (o más bien recibir la respuesta) se elimina del flujo de ejecución normal. En su ejemplo, .sendregresa inmediatamente y la siguiente declaració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 regrese, el oyente que definió aún no se ejecutó, lo que significa que el valor que está devolviendo no se definió.

Aquí hay una analogía simple:

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

(Violín)

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

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

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

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

Esto se llama CPS . Básicamente, estamos pasando getFiveuna acción para que se realice 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 cual debería alertar a "5" a la pantalla. (Violín) .

Soluciones posibles

Básicamente, hay dos formas de resolver esto:

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

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

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

XMLHttpRequest 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 tienes que hacerlo, puedes pasar una bandera. Así es como :

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

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

2. Reestructurar código

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 foose complete.

Asi que:

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

se convierte en:

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

Aquí pasamos una función anónima, pero 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 foo para actuar en consecuencia.

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

(violín)

Ahora hemos hecho que nuestra función foo acepte una acción para ejecutar cuando AJAX se complete con éxito. Podemos extender esto aún más comprobando si el estado de respuesta no es 200 y actuando en consecuencia (crear un controlador de errores 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.

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

Si no usa jQuery y quiere un XMLHttpRequest 2 corto y agradable que funcione en los navegadores modernos y también en los navegadores móviles, le sugiero que lo use de esta manera:

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

Como puedes ver:

  1. Es más corto que todas las demás funciones enumeradas.
  2. La devolución de llamada se establece directamente (por lo que no hay cierres adicionales innecesarios).
  3. Utiliza la nueva carga (por lo que no tiene que verificar el estado listo y el estado)
  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()devuelves 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 más fácil.

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

Consulte las características avanzadas de XMLHttpRequest .

Soportaba 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.

Usar el XMLHttpRequestnombre de la variable es otro gran error, ya que necesita ejecutar la devolución de llamada dentro de los cierres onload/oreadystatechange, o lo perderá.


Ahora, si desea algo más complejo usando POST y FormData, puede ampliar fácilmente esta función:

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

De nuevo... es una función muy corta, pero hace 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 menciona en el comentario, el uso de error && sincrónico rompe por completo el punto de la respuesta. ¿Cuál es una buena manera corta de usar Ajax de la manera adecuada?

Controlador de errores

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

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

En el 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 realmente obtener 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 el tipo de respuesta en el búfer de matriz de blobs, o lo que sea...

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

Incluso si pasa 'fdggdgilfdghfldj' como datos de formulario, no arrojará un error.

En el primer caso, el error está dentro de displayAjax()under this.statusTextas 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 ningún código de error.

Solo existe el this.typeque está configurado en 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().

Entonces: no hay necesidad de verificaciones de errores si puede copiar y pegar la URL correctamente. ;)

PD: Como primera prueba escribí x('x', displayAjax)..., y obtuvo una respuesta total...??? Así que revisé la carpeta donde se encuentra el HTML y había un archivo llamado 'x.xml'. Entonces, incluso si olvida la extensión de su archivo, XMLHttpRequest 2 LO ENCONTRARÁ . 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 asíncrona. (Sí, con el bucle setTimeout... pero ¿en serio?)

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

Solo si tiene una página donde carga siempre el mismo XML/JSON o lo que sea, solo necesita una función. En ese caso, modifica un poco la función Ajax y reemplaza b con tu 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, con solo GET...

Pero puedes hacer muchas cosas con XMLHttpRequest 2:

Creé un administrador de descargas (usando rangos en ambos lados con currículum, lector de archivos y sistema de archivos), varios convertidores de redimensionamiento de imágenes usando lienzo, llene bases de datos SQL web 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 manera fácil).

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

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

Tu código debería ser algo así como esto:

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

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

Felix Kling hizo un buen trabajo al escribir una respuesta para las personas que usan jQuery con devoluciones de llamada para Ajax. Tengo una respuesta para XHR nativo. Esta respuesta es para el uso genérico de promesas, ya sea en el frontend o en el backend.


El problema central

El modelo de concurrencia 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 está devolviendo datael thencontrolador que definió aún no se ejecutó. Esto, a su vez, significa que el valor que está devolviendo no se ha establecido en el valor correcto 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 = 5parte aún no se ha ejecutado. Probablemente se ejecutará 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 tuviera la oportunidad de decirle a su código cuál es ese valor.

Una posible solución a este problema es codificar reactivamente , diciéndole a su programa qué hacer cuando se complete el cálculo. Las promesas activan esto activamente al ser temporales (sensibles al tiempo) por naturaleza.

Resumen rápido de las promesas

Una Promesa es un valor en el tiempo . Las promesas tienen estado. Comienzan como pendientes sin valor y pueden liquidarse a:

  • cumplida , 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 los errores. thenlos controladores permiten el encadenamiento de llamadas. Las promesas se crean mediante el uso de API que las devuelven . Por ejemplo, el reemplazo más moderno de Ajax o las promesas de devolución fetchde jQuery .$.get

Cuando invocamos .thenuna promesa y devolvemos algo de ella, obtenemos una promesa por el valor procesado . Si cumplimos otra promesa obtendremos cosas increíbles, pero aguantemos nuestros caballos.

con promesas

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

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

Ahora, después de que convertimos 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 podemos hacer debido al modelo de concurrencia, estamos devolviendo un contenedor para un valor con el que podemos desenvolverthen . Es como una caja con la que se puede abrir then.

Aplicando esto

Esto es lo mismo 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`
})

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

ES2015 (ES6)

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

function* foo(){ // Notice the star. This is ES6, so new browsers, 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 para muchas posibilidades, hay un caso interesante en particular.

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

Este truco algo complicado, pero muy poderoso, nos permite escribir código asíncrono de manera síncrona. Hay varios "corredores" que hacen esto por ti. Escribir uno es unas pocas líneas de código, pero está más allá del alcance de esta respuesta. Usaré Bluebird's Promise.coroutineaquí, pero hay otros envoltorios 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í misma, que podemos consumir de otras rutinas. 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 está aún más estandarizado. Hay varias propuestas ahora mismo, pero en todas puedes awaitprometer. Esto es simplemente "azúcar" (sintaxis más agradable) para la propuesta de 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 la misma manera :)

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 cualquier cosa 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.

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

ingrese la descripción de la imagen aquí

La solución más simple es crear una función de JavaScript y llamarla para la successdevolución de llamada de 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);
});

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.

Puedes encontrar una buena explicación aquí también.

Un ejemplo que se encuentra en la documentación mencionada 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 posteriores

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 es compatible con ES6 Promises nativo , si desea usarlo, es posible que necesite un complemento para eso.

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

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 realizar 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 doSomethingAsyncaún no se han ejecutado en el momento en que intenta utilizar 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: realice las operaciones en paralelo (superpuestas) o en serie (una tras otra en secuencia).

Paralelo

Puede iniciarlos todos y realizar un seguimiento de cuántas devoluciones de llamadas espera, y luego usar los resultados cuando haya recibido tantas devoluciones de llamadas:

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 eliminar expectingy simplemente 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 desordenados (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 desde una función? Como han señalado las otras respuestas, no puedes; debe 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 en su Promiselugar:

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 pasara errores, usaríamos rejectpara rechazar la promesa cuando recibimos un error).

Ejemplo:

function doSomethingWith(theArray) {
    return new Promise(function(resolve) {
        var results = [];
        var expecting = theArray.length;
        theArray.forEach(function(entry, index) {
            doSomethingAsync(entry, function(result) {
                results[index] = result;
                if (--expecting === 0) {
                    // Done!
                    resolve(results);
                }
            });
        });
    });
}
doSomethingWith([1, 2, 3]).then(function(results) {
    console.log("Results:", 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, podrías hacer un envoltorio para doSomethingAsyncque devuelva una promesa, y luego hacer 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 un tercer argumento, puede pasarlo 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 todas están resueltas, o rechaza su promesa cuando la primera de las promesas que le da es rechazada.

Serie

Suponga que no quiere que las operaciones sean en paralelo. Si desea ejecutarlos uno tras otro, debe esperar a que se complete cada operación antes de comenzar con 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);
});

(Dado que estamos haciendo el trabajo en serie, podemos usar results.push(result)porque 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, de nuevo, cree un envoltorio doSomethingAsyncque le dé una promesa y haga lo siguiente...)

Si doSomethingAsyncle da una Promesa, si puede usar la sintaxis ES2017+ (quizás con un transpilador como Babel ), puede 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 de 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 las funciones de flecha ES2015+ :

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

Ejemplo:

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

Echa 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 devolver res.data.value). Por lo tanto, 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/

Modo ES6 (asincrónico - en espera)

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

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

Entonces, si está utilizando Angular , React o cualquier otro marco que haga un concepto de almacenamiento o enlace de datos bidireccional, este problema simplemente se solucionó para usted, por lo que en palabras simples, 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 asignará al nuevo valor que responde a su llamada Ajax ...

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

Puede usar una devolución de llamada, una promesa y un observable reciente 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 promesas y observables, que son formas más nuevas de hacer estas cosas asincrónicas.

Es un problema muy común al que nos enfrentamos cuando luchamos con los "misterios" de JavaScript. Permítanme tratar de desmitificar este misterio hoy.

Comencemos con una función de JavaScript simple:

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

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

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

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

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

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

Ahí vas; ¡ese retraso simplemente rompió 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 'woohoo'. Pero como puede ver, el valor que se asigna a la barra es el material devuelto inmediatamente por foo(), que es nada, es decir, solo undefined.

Entonces, ¿cómo abordamos este problema?

Pidamos a nuestra función una promesa . La promesa se trata realmente de lo que significa: significa que la función le garantiza proporcionar cualquier salida que obtenga en el futuro. Entonces, 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 al resolvevalor (que pretende devolver). Por lo tanto, en resumen, resuelve valor en lugar de devolver , en funciones asíncronas.

ACTUALIZAR (Promesas con async/await)

Aparte de usar then/catchpara trabajar con promesas, existe un enfoque más. La idea es reconocer una función asíncrona y luego esperar a que se resuelvan las promesas, antes de pasar a la siguiente línea de código. Todavía es solo promisesdebajo del capó, pero con un enfoque sintáctico diferente. Para hacer las cosas más claras, puede encontrar una comparación a continuación:

entonces/atrapar versión:

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

versión asíncrona/en espera:

  async function saveUsers(){
     try{
        let users = await getUsers()
        saveSomewhere(users);
     }
     catch(err){
        console.error(err);
     }
  }

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

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 asíncrona. Esto permite que el resultado esté disponible incluso después del trabajo asíncrono.

Yo uso mucho este enfoque. Me interesaría saber qué tan bien funciona este enfoque cuando se trata de conectar el resultado a través de módulos consecutivos.

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 pasando por async1; verifique 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.

Fibersayuda 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í .

El siguiente ejemplo que he escrito muestra cómo

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

Este ejemplo de trabajo es autónomo. Definirá un objeto de solicitud simple que utiliza 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 ExecutionProfileBlockforma asíncrona.

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

NOTA Las API de Spotify recientes searchrequerirán que se especifique un token de acceso en los encabezados de solicitud:

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

Entonces, para ejecutar el siguiente ejemplo, debe colocar su token de acceso en los encabezados de 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 extensamente esta solución aquí .

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

JavaScript is single threaded.

El navegador se puede dividir en tres partes:

  1. Bucle de eventos

  2. API web

  3. Cola de eventos

El bucle de eventos se ejecuta para siempre, es decir, una especie de bucle infinito. La cola de eventos es donde todas sus funciones se envían a algún evento (ejemplo: hacer clic).

Esto se lleva a cabo uno por uno 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 ejecutar el primero. Esto significa que la ejecución de una función no comienza hasta que la función anterior 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. Empujamos la función serverRequest() en la cola primero 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, por lo tanto, bloqueamos nuestra página.

Ahí es donde entra en juego la API web. Toma esta función del bucle de eventos y trata con el servidor liberando el bucle de eventos, para que podamos ejecutar la siguiente función de la cola.

La siguiente función en la cola es utiliseData(), que entra 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() tenía una declaración de retorno en el código. Cuando recuperemos los datos de la API web del servidor, los colocará en la cola al final de la cola.

Como 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 asíncrona.

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 JavaScript.info

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 indicará a JavaScript que espere que la promesa se resuelva en un valor (como la respuesta HTTP)
  • Agregue la palabra clave 'async' a la función principal

Aquí hay una versión de trabajo de su código:

(async function(){

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

})()

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

Puede usar 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 sencillo:

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

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 colocará 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 no se promete la función subyacente

Paso 1. Envuelva la función con una devolución de llamada en el contenedor nsynjs-aware (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. Poner 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í .

ECMAScript 6 tiene 'generadores' que le permiten programar fácilmente en un estilo asíncrono.

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

Nos encontramos en un universo que parece progresar a lo largo de una dimensión que llamamos "tiempo". Realmente no entendemos qué es el tiempo, pero hemos desarrollado abstracciones y vocabulario que nos permiten razonar y hablar sobre é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. Luego, otras cosas deben suceder después de que esas primeras cosas eventualmente ocurran. Esta es la noción básica llamada "asincronía". En nuestro mundo cada vez más interconectado, el caso más común de asincronía es esperar a que algún sistema remoto responda a alguna solicitud.

Considere un ejemplo. Llamas al lechero y pides un poco de leche. Cuando llegue, querrás ponerlo en tu café. No puedes poner la leche en tu café en este momento, porque aún 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 finalice antes de ejecutarse put_in_coffee. En otras palabras, no sabe que order_milkes asíncrono , es algo que no dará como resultado leche hasta algún tiempo en el 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 asíncrona, que luego invocará cuando se haya completado. su tarea en algún momento 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 rápidamente difícil de manejar 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, es decir, beber el café. Termino necesitando escribir algo como esto:

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

donde paso put_in_coffeetanto la leche para ponerla como la acción ( drink_coffee) para ejecutar una vez que se ha puesto la leche. Dicho 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);
}

Introduce 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 sucederá en el futuro, o que quizás nunca suceda. Las promesas tienen un solo método, llamado then, al que pasa una acción para que se ejecute cuando se haya realizado el resultado que representa la promesa.

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

order_milk() . then(put_in_coffee)

Una ventaja de esto es que podemos encadenarlos para crear secuencias de ocurrencias futuras ("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 $.ajaxya devuelve una especie de promesa. (En la práctica, sin entrar en detalles, preferiríamos envolver esta llamada para devolver una promesa real, o usar alguna alternativa para $.ajaxque lo haga). Ahora, si queremos cargar el archivo y esperar a que termine y entonces 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 a then, por lo que a menudo es útil usar las funciones de flecha de estilo ES6 más compactas:

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

la asyncpalabra clave

Pero todavía hay algo vagamente insatisfactorio en tener que escribir el código de una manera si es sincrónico y de una manera bastante diferente si es asincrónico. Para síncrono, 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 bueno si hubiera alguna forma de decirle eso a JavaScript? Resulta que existe la awaitpalabra clave, utilizada dentro de un tipo especial de función llamada función "asincrónica". Esta característica es parte de la próxima versión de ECMAScript (ES), pero ya está disponible en transpiladores como Babel con los ajustes preestablecidos correctos. 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 tu caso, podrías escribir algo como

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

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

En este hilo se han dado varias soluciones. 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()seguirá sin devolver nada útil. Sin embargo, el resultado de la llamada asíncrona ahora se almacenará en result.response.

Estos son algunos enfoques para trabajar con solicitudes asincrónicas:

  1. Objeto de promesa del navegador
  2. P - Una biblioteca prometedora para JavaScript
  3. A+ Promesas.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();

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

1. Un primer paso de tropiezo

Como para muchos otros, mi encuentro con las llamadas asincrónicas fue desconcertante al principio.
No recuerdo los detalles, pero es posible que haya intentado algo como:

let result;

$.ajax({
  url: 'https://jsonplaceholder.typicode.com/todos/1',
  success: function (response) {
    console.log('\nInside $.ajax:');
    console.log(response);
    result = response;
  }
});

console.log('Finally, the result: ' + result);
.as-console-wrapper { max-height: 100% !important; top: 0; }
<script src=
"https://ajax.googleapis.com/ajax/libs/jquery/3.6.0/jquery.min.js"></script>

¡Vaya! ¡ La salida de la línea console.log('Finally, the result: ' + result); que pensé que se imprimiría en último lugar , en realidad se imprime antes que la otra salida! – Y no contiene el resultado: simplemente imprime undefined. 1 ¿Cómo es que?

Una idea útil

Recuerdo claramente mi primer ¡ajá! momento sobre cómo entender las llamadas asíncronas.
Fue este comentario que decía:
en realidad no desea obtener los datos de una devolución de llamada;
¡Desea obtener su acción de necesidad de datos en la devolución de llamada!
2
Esto es obvio en el ejemplo anterior.
Pero, ¿sigue siendo posible escribir código después de la llamada asincrónica que se ocupa de la respuesta una vez que se ha completado?

2. JavaScript simple y una función de devolución de llamada

¡ La respuesta es sí! - Es posible.
Una alternativa es el uso de una función de devolución de llamada en un estilo de paso de continuación: 3

const url = 'https://jsonplaceholder.typicode.com/todos/2';

function asynchronousCall (callback) {
  const request = new XMLHttpRequest();
  request.open('GET', url);
  request.send();
  request.onload = function () {
    if (request.readyState === request.DONE) {
      console.log('The request is done. Now calling back.');
      callback(request.responseText);
    }
  };
}

asynchronousCall(function (result) {
  console.log('This is the start of the callback function. Result:');
  console.log(result);
  console.log('The callback function finishes on this line. THE END!');
});

console.log('LAST in the code, but executed FIRST!');
.as-console-wrapper { max-height: 100% !important; top: 0; }

Note how the function asynchronousCall is void. It returns nothing. Instead, by calling asynchronousCall with an anonymous callback function (asynchronousCall(function (result) {...), this function executes the desired actions on the result, but only after the request has completed – when the responseText is available.

Running the above snippet shows how I will probably not want to write any code after the asyncronous call (such as the line LAST in the code, but executed FIRST!).
Why? – Because such code will happen before the asyncronous call delivers any response data.
Doing so is bound to cause confusion when comparing the code with the output.

3. Promise with .then() – or async/await

The .then() construct was introduced in the ECMA-262 6th Edition in June 2015, and the async/await construct was introduced in the ECMA-262 8th Edition in June 2017.
The code below is still plain JavaScript, replacing the old-school XMLHttpRequest with Fetch. 4

fetch('http://api.icndb.com/jokes/random')
  .then(response => response.json())
  .then(responseBody => {
    console.log('.then() - the response body:');
    console.log(JSON.stringify(responseBody) + '\n\n');
  });

async function receiveAndAwaitPromise () {
  const responseBody =
    (await fetch('http://api.icndb.com/jokes/random')).json();
  console.log('async/await:');
  console.log(JSON.stringify(await responseBody) + '\n\n');
}

receiveAndAwaitPromise();
.as-console-wrapper { max-height: 100% !important; top: 0; }

A word of warning is warranted if you decide to go with the async/await construct. Note in the above snippet how await is needed in two places. If forgotten in the first place, there will be no output. If forgotten in the second place, the only output will be the empty object, {} (or [object Object] or [object Promise]).
Forgetting the async prefix of the function is maybe the worst of all – the output will be "SyntaxError: missing ) in parenthetical" – no mentioning of the missing async keyword.

4. Promise.all – array of URLs 5

Suppose we need to request a whole bunch of URLs. I could send one request, wait till it responds, then send the next request, wait till it responds, and so on ...
Aargh! – That could take a loong time. Wouldn't it be better if I could send them all at once, and then wait no longer than it takes for the slowest response to arrive?

As a simplified example, I will use:

urls = ['https://jsonplaceholder.typicode.com/todos/2',
        'https://jsonplaceholder.typicode.com/todos/3']

The JSONs of the two URLs:

{"userId":1,"id":2,"title":"quis ut nam facilis et officia qui",
 "completed":false}
{"userId":1,"id":3,"title":"fugiat veniam minus","completed":false}

The goal is to get an array of objects, where each object contains the title value from the corresponding URL.

To make it a little more interesting, I will assume that there is already an array of names that I want the array of URL results (the titles) to be merged with:

namesonly = ['two', 'three']

The desired output is a mashup combining namesonly and urls into an array of objects:

[{"name":"two","loremipsum":"quis ut nam facilis et officia qui"},
{"name":"three","loremipsum":"fugiat veniam minus"}]

where I have changed the name of title to loremipsum.

const namesonly = ['two','three'];

const urls = ['https://jsonplaceholder.typicode.com/todos/2',
  'https://jsonplaceholder.typicode.com/todos/3'];

Promise.all(urls.map(url => fetch(url)
  .then(response => response.json())
  .then(responseBody => responseBody.title)))
  .then(titles => {
    const names = namesonly.map(value => ({ name: value }));
    console.log('names: ' + JSON.stringify(names));
    const latins = titles.map(value => ({ loremipsum: value }));
    console.log('latins:\n' + JSON.stringify(latins));
    const result =
      names.map((item, i) => Object.assign({}, item, latins[i]));
    console.log('result:\n' + JSON.stringify(result));
  });
.as-console-wrapper { max-height: 100% !important; top: 0; }

All the above examples are short and succinctly convey how asynchronous calls may be used on toyish APIs. Using small APIs works well to explain concepts and working code, but the examples might be a bit of dry runs.

The next section will show a more realistic example on how APIs may be combined to create a more interesting output.

5. How to visualize a mashup in Postman 6

The MusicBrainz API has information about artists and music bands.
An example – a request for the British rock band Coldplay is:
http://musicbrainz.org/ws/2/artist/cc197bad-dc9c-440d-a5b5-d52ba2e14234?&fmt=json&inc=url-rels+release-groups.
The JSON response contains – among other things – the 25 earliest album titles by the band. This information is in the release-groups array. The start of this array, including its first object is:

...
  "release-groups": [
    {
      "id": "1dc4c347-a1db-32aa-b14f-bc9cc507b843",
      "secondary-type-ids": [],
      "first-release-date": "2000-07-10",
      "primary-type-id": "f529b476-6e62-324f-b0aa-1f3e33d313fc",
      "disambiguation": "",
      "secondary-types": [],
      "title": "Parachutes",
      "primary-type": "Album"
    },
...

This JSON snippet shows that the first album by Coldplay is Parachutes. It also gives an id, in this case 1dc4c347-a1db-32aa-b14f-bc9cc507b843, which is a unique identifier of the album.

This identifier can be used to make a lookup in the Cover Art Archive API:
http://coverartarchive.org/release-group/1dc4c347-a1db-32aa-b14f-bc9cc507b843. 7

For each album, the JSON response contains some images, one of which is the front cover of the album. The first few lines of the response to the above request:

{
  "images": [
    {
      "approved": true,
      "back": false,
      "comment": "",
      "edit": 22132705,
      "front": true,
      "id": 4086974851,
      "image": "http://coverartarchive.org/release/435fc965-9121-461e-b8da-d9b505c9dc9b/4086974851.jpg",
      "thumbnails": {
        "250": "http://coverartarchive.org/release/435fc965-9121-461e-b8da-d9b505c9dc9b/4086974851-250.jpg",
        "500": "http://coverartarchive.org/release/435fc965-9121-461e-b8da-d9b505c9dc9b/4086974851-500.jpg",
        "1200": "http://coverartarchive.org/release/435fc965-9121-461e-b8da-d9b505c9dc9b/4086974851-1200.jpg",
        "large": "http://coverartarchive.org/release/435fc965-9121-461e-b8da-d9b505c9dc9b/4086974851-500.jpg",
= = >   "small": "http://coverartarchive.org/release/435fc965-9121-461e-b8da-d9b505c9dc9b/4086974851-250.jpg"
    },
...

Of interest here is the line "small": "http://coverartarchive.org/release/435fc965-9121-461e-b8da-d9b505c9dc9b/4086974851-250.jpg".
That URL is a direct link to the front cover of the Parachutes album.

The code to create and visualize the mashup

The overall task is to use Postman to visualize all the album titles and front covers of a music band. How to write code to achieve this has already been described in quite some detail in an answer to the question How can I visualize an API mashup in Postman? – Therefore I will avoid lengthy discussions here and just present the code and a screenshot of the result:

const lock = setTimeout(() => {}, 43210);
const albumsArray = [];
const urlsArray = [];
const urlOuter = 'https://musicbrainz.org/ws/2/artist/' +
  pm.collectionVariables.get('MBID') + '?fmt=json&inc=url-rels+release-groups';
pm.sendRequest(urlOuter, (_, responseO) => {
  const bandName = responseO.json().name;
  const albums = responseO.json()['release-groups'];
  for (const item of albums) {
    albumsArray.push(item.title);
    urlsArray.push('https://coverartarchive.org/release-group/' + item.id);
  }
  albumsArray.length = urlsArray.length = 15;
  const images = [];
  let countDown = urlsArray.length;
  urlsArray.forEach((url, index) => {
    asynchronousCall(url, imageURL => {
      images[index] = imageURL;
      if (--countDown === 0) { // Callback for ALL starts on next line.
        clearTimeout(lock); // Unlock the timeout.
        const albumTitles = albumsArray.map(value => ({ title: value }));
        const albumImages = images.map(value => ({ image: value }));
        const albumsAndImages = albumTitles.map(
          (item, i) => Object.assign({}, item, albumImages[i]));
        const template = `<table>
          <tr><th>` + bandName + `</th></tr>
          {{#each responseI}}
          <tr><td>{{title}}<br><img src="{{image}}"></td></tr>
          {{/each}}
        </table>`;
        pm.visualizer.set(template, { responseI: albumsAndImages });
      }
    });
  });
  function asynchronousCall (url, callback) {
    pm.sendRequest(url, (_, responseI) => {
      callback(responseI.json().images.find(obj => obj.front === true)
        .thumbnails.small); // Individual callback.
    });
  }
});


The result and documentation

Resultado y documentación en Postman


How to download and run the Postman Collection

Running the Postman Collection should be straightforward.
Assuming you are using the desktop version of Postman, do as follows:

  1. Download and save
    http://henke.atwebpages.com/postman/mbid/MusicBands.pm_coll.json
    in a suitable place on your hard drive.

  2. In Postman, Ctrl + O > Upload Files > MusicBands.pm_coll.json > Import.
    You should now see MusicBands among your collections in Postman.

  3. Collections > MusicBands > DummyRequest > Send. 8

  4. In the Postman Response Body, click Visualize.

  5. You should now be able to scroll 15 albums as indicated by the screenshot above.

References


1 Expressed by the original poster as: they all return undefined.
2 If you think asynchronous calls are confusing, consider having a look at some questions and answers about asynchronous calls to see if that helps.
3 The name XMLHttpRequest is as misleading as the X in AJAX – these days the data format of Web APIs is ubiquitously JSON, not XML.
4 Fetch returns a Promise. I was surprised to learn that neither XMLHttpRequest nor Fetch are part of the ECMAScript standard. The reason JavaScript can access them here is because the web browser provides them. The Fetch Standard and the XMLHttpRequest Standard are both upheld by the Web Hypertext Application Technology Working Group (WHATWG) that was formed in June 2004.
5 This section borrows a lot from How can I fetch an array of URLs with Promise.all?.
6 This section relies heavily on How can I visualize an API mashup in Postman?.
7 This URL is automatically redirected to: https://ia800503.us.archive.org/29/items/mbid-435fc965-9121-461e-b8da-d9b505c9dc9b/index.json.
8 If you get an error, Something went wrong while running your scripts, try hitting Send again.

Using Promise

The most perfect answer to this question is using 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);
  });
}

Usage

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

But wait...!

There is a problem with using promises!

Why should we use our own custom Promise?

I was using this solution for a while until I figured out there is an error in old browsers:

Uncaught ReferenceError: Promise is not defined

So I decided to implement my own Promise class for ES3 to below JavaScript compilers if it's not defined. Just add this code before your main code and then safely use Promise!

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

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

The question was:

How do I return the response from an asynchronous call?

which can be interpreted as:

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")

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.