una - programacion asincrona javascript




¿Cómo devuelvo la respuesta de una llamada asíncrona? (20)

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

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

function foo() {
    var result;

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

    return result;
}

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

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

→ Si ya entiende el problema, vaya a las posibles soluciones a continuación.

El problema

La A en Ajax significa asynchronous . Eso significa que el envío de la solicitud (o más bien la recepción de la respuesta) se elimina del flujo de ejecución normal. En su ejemplo, $.ajax devuelve inmediatamente y la siguiente declaración, return result; , se ejecuta antes de que la función que pasó como llamada de devolución de llamada success se haya ejecutado.

Aquí hay una analogía que, con suerte, hace que la diferencia entre flujo síncrono y asíncrono sea más clara:

Sincrónico

Imagina que haces una llamada telefónica a un amigo y le pides que te busque algo. Aunque puede tardar un rato, esperas en el teléfono y miras al espacio, hasta que tu amigo te da la respuesta que necesitabas.

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

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

var item = findItem();

// Do something with item
doSomethingElse();

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

Asincrónico

Llamas a tu amigo otra vez por la misma razón. Pero esta vez le dices que tienes prisa y que debería devolverte la llamada desde tu teléfono móvil. Cuelgas, sales de casa y haces lo que planeas hacer. Una vez que tu amigo te devuelve la llamada, estás tratando con la información que te dio.

Eso es exactamente lo que está sucediendo cuando haces una solicitud de Ajax.

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

En lugar de esperar la respuesta, la ejecución continúa de inmediato y se ejecuta la instrucción después de la llamada Ajax. Para obtener la respuesta con el tiempo, proporciona una función para que se llame una vez que se recibió la respuesta, una devolución de llamada (¿nota algo? Cualquier declaración que venga después de esa llamada se ejecuta antes de que se llame la devolución de llamada.

Solución (s)

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

¿Por qué es malo lo preguntas?

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

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

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

  • Promesas con async/await await (ES2017 +, disponible en navegadores más antiguos si utiliza un transpiler o regenerador)
  • Callbacks (popular en el nodo)
  • Promesas con then() (ES2015 +, disponible en navegadores más antiguos si utiliza una de las muchas bibliotecas de promesa)

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

ES2017 +: Promesas con async/await

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

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

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

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

Aquí hay un ejemplo que se basa en el retraso anterior:

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

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


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

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

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

Deje que las funciones acepten devoluciones de llamada

Una devolución de llamada es simplemente una función pasada a otra función. Esa otra función puede llamar a la función pasada siempre que esté lista. En el contexto de un proceso asíncrono, la devolución de llamada se llamará siempre que se realice el proceso asíncrono. Por lo general, el resultado se pasa a la devolución de llamada.

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

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

se convierte en

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

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

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

foo(myCallback);

foo sí se define de la siguiente manera:

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

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

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

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

Es más fácil escribir código usando devoluciones de llamada de lo que parece. Después de todo, JavaScript en el navegador está fuertemente controlado por eventos (eventos DOM). Recibir la respuesta Ajax no es más que un evento.
Podrían surgir dificultades cuando tenga que trabajar con un 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 then()

La then() es una nueva característica de ECMAScript 6 (ES2015), pero ya tiene un buen soporte de navegador . También hay muchas bibliotecas que implementan la API Promises estándar y proporcionan métodos adicionales para facilitar el uso y la composición de las 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 ( rechaza ), notifica a todos sus "oyentes" que desean acceder a este valor.

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

Aquí hay un ejemplo simple de usar una promesa:

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

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

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

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

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

Describir todas las ventajas que ofrece la promesa va más allá del alcance de esta respuesta, pero si escribe un nuevo código, debe considerarlos 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 la API de Promise fuera estandarizada). 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: Promesa gotchas

Tenga en cuenta que las promesas y los objetos diferidos son solo contenedores para un valor futuro, no son el valor en sí mismo. Por ejemplo, supongamos que tienes 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 de asincronía anteriores. Específicamente, $.ajax() no congela el código mientras comprueba la página '/ password' en su servidor: envía una solicitud al servidor y, mientras espera, devuelve inmediatamente un objeto AQax diferido de jQuery, no la respuesta de el servidor. Eso significa que la instrucción if siempre obtendrá este objeto diferido, lo tratará como true y procederá como si el usuario hubiera iniciado sesión. No es bueno.

Pero la solución es fácil:

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

No recomendado: Llamadas síncronas "Ajax".

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

Sin jQuery

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

jQuery

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

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

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

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


Js es un solo hilo.

El navegador se puede dividir en tres partes:

1) Loop evento

2) API web

3) Cola de eventos

Event Loop se ejecuta para siempre, es decir, un tipo de bucle infinito. La cola de eventos es donde todas las funciones se insertan en algún evento (ejemplo: clic). Esto se realiza de la cola uno a uno y se coloca en el bucle de eventos que ejecuta esta función y se prepara por sí mismo para el siguiente después de que se ejecute el primero. Esto significa que la ejecución de una función no se inicia hasta que la función anterior a la cola se ejecuta en el bucle de eventos.

Ahora pensemos que pusimos dos funciones en una cola, una es para obtener datos del servidor y otra utiliza esos datos. Primero pusimos la función serverRequest () en la cola y luego la función utiliseData (). La función serverRequest entra en el bucle de eventos y realiza una llamada al servidor, ya que nunca sabemos cuánto tiempo tomará obtener los datos del servidor, por lo que se espera que este proceso tarde un tiempo, por lo que ocupamos nuestro bucle de eventos, por lo que cuelga nuestra página. La API entra en función, toma esta función del bucle de eventos y se ocupa de que el servidor libere 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 bucle pero debido a que no hay datos disponibles, va el desperdicio y la ejecución de la siguiente función continúa hasta el final de la cola (esto se denomina 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 un código, cuando obtengamos datos de la API web del servidor la pondremos en cola al final de la cola. Como se envía al final de la cola, no podemos utilizar sus datos ya que no hay ninguna función en nuestra cola para utilizar estos datos Por lo tanto, no es posible devolver algo desde Async Call.

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

Una imagen de una de las respuestas aquí, Explica correctamente el uso de la devolución de llamada ... Damos nuestra función (función que utiliza datos devueltos desde el servidor) al servidor de llamada de función.

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

Lea aquí los nuevos métodos en ECMA (2016/17) para realizar una llamada asíncrona (@Felix Kling Answer en la parte superior) https://.com/a/14220323/7579856


Si no estás usando jQuery en tu código, esta respuesta es para ti.

Su código debe ser algo 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. Decidí ofrecer una alternativa para las personas que no lo son.

( Tenga en cuenta que para aquellos que utilizan la nueva API de fetch , Angular o promesas, he agregado otra respuesta a continuación )

A lo que te enfrentas

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

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

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

Aquí hay una simple analogía.

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

(Fiddle)

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

Una posible solución a este problema es codificar de forma activa , indicando 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, pasamos a getFive una acción para realizar cuando se completa, le decimos a nuestro código cómo reaccionar cuando se completa un evento (como nuestra llamada AJAX, o en este caso, el tiempo de espera).

El uso sería:

getFive(onComplete);

Lo que debería alertar "5" a la pantalla (Fiddle) .

Soluciones posibles

Básicamente hay dos maneras de resolver esto:

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

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

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

XMLHttpRequest admite comunicaciones síncronas y asíncronas. En general, sin embargo, las solicitudes asíncronas deberían preferirse a las solicitudes sincrónicas por razones de rendimiento.

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

Si tienes que hacerlo, puedes pasar una bandera: Aquí es cómo:

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

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

2. Reestructurar el código.

Deja que tu función acepte una devolución de llamada. En el código de ejemplo, se puede hacer que foo acepte una devolución de llamada. Le diremos a nuestro código cómo reaccionar cuando foo 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 también podríamos pasar una referencia a una función existente, haciendo que parezca:

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

Para obtener más detalles sobre cómo se hace este tipo de diseño de devolución de llamada, verifique 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();
}

(fiddle)

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

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


Respuesta de 2017: ahora puede hacer exactamente lo que quiera en cada navegador y nodo actual

Esto es bastante simple:

  • Devolver una promesa
  • Use await , que le indicará a JavaScript que espere a que la promesa se resuelva en un valor (como la respuesta HTTP)
  • Agregue la palabra clave async/await 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 el nodo 8


Aquí hay algunos enfoques para trabajar con solicitudes asíncronas:

  1. then()
  2. Q - Una biblioteca de promesa para JavaScript
  3. A + Promises.js
  4. jQuery aplazado
  5. API XMLHttpRequest
  6. Usando el concepto de devolución de llamada - Como implementación en 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();


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

Si no usa jQuery y desea un buen XMLHttpRequest 2 corto que funcione en los navegadores modernos y también en los navegadores móviles, sugiero usarlo de esta manera:

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

Como puedes ver:

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

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

Lo más simple:

this.response

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

e.target.response

Ejemplo:

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

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

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

Nada mas facil

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

Echa un vistazo a las características avanzadas de XMLHttpRequest

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

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

El uso del nombre de la variable XMLHttpRequest es otro gran error, ya que necesita ejecutar la devolución de llamada dentro de los cierres de onload / oreadystatange o si no lo perdió.

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

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

Nuevamente ... es una función muy corta, pero se obtiene y se publica.

Ejemplos de uso:

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

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

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

O establece algunos valores personalizados:

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

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

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

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

Manejador de errores

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

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

En la secuencia de comandos anterior, tiene un controlador de errores que está definido estáticamente para que no comprometa la función. El controlador de errores también se puede utilizar para otras funciones.

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

Los manejadores de errores pueden ser útiles si establece encabezados personalizados, establece responseType en blob array buffer o lo que sea ...

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

Incluso si pasas 'fdggdgilfdghfldj' como formdata no generará ningún error.

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

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

dominio cruzado no permitido lanza error automáticamente.

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

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

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

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

PD: Como la primera prueba escribí x ('x', displayAjax) ..., y obtuve 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 síncrono

No hagas eso

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

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

Ahora puedes hacer

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

No hay otra forma de hacer esto de forma no asíncrona. (Sí, con setTimeout loop ... pero en serio?)

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

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

Las funciones anteriores son para uso básico.

Si quieres 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:

Hice un administrador de descargas (usando rangos en ambos lados con curriculum vitae, lector de archivos, sistema de archivos), varios convertidores de resizadores de imágenes usando lienzos, rellenando bases de datos websql con base64images y mucho más ... Pero en estos casos, debe crear una función solo para ese propósito ... a veces necesitas un blob, búferes de matriz, puedes 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.)


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

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

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

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

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


Veamos primero el bosque antes de mirar los árboles.

Hay muchas respuestas informativas con grandes detalles aquí, no repetiré ninguna de ellas. La clave para la programación en JavaScript es tener primero el modelo mental correcto de ejecución general.

  1. Sus puntos de entrada se ejecutan como resultado de un evento. Por ejemplo, una etiqueta de script con código se carga en el navegador. (En consecuencia, es por esto que es posible que deba preocuparse por la preparación de la página para ejecutar su código si requiere que primero se construyan elementos dom, etc.)
  2. Su código se ejecuta hasta su finalización, independientemente de las llamadas asíncronas que realice, sin ejecutar ninguna de sus devoluciones de llamada, incluidas las solicitudes de XHR, los tiempos de espera establecidos, los controladores de eventos de dom, etc. su turno para ejecutarse después de otros eventos que se activaron ha terminado su ejecución.
  3. Cada devolución de llamada individual a una solicitud XHR, establecer un tiempo de espera o dom el evento una vez invocado se ejecutará hasta su finalización.

La buena noticia es que si entiendes bien este punto, nunca tendrás que preocuparte por las condiciones de la carrera. En primer lugar, debe tener en cuenta cómo desea organizar su código como esencialmente la respuesta a diferentes eventos discretos y cómo desea agruparlos en una secuencia lógica. Puede usar promesas o un nuevo nivel de async / await como herramientas para ese fin, o puede hacer suyas propias.

Pero no debe usar ninguna herramienta táctica para resolver un problema hasta que esté cómodo con el dominio del problema real. Dibuje un mapa de estas dependencias para saber qué debe ejecutar cuándo. Intentar un enfoque ad-hoc para todas estas devoluciones de llamada simplemente no va a servirle bien.


El siguiente ejemplo que he escrito muestra cómo

  • Manejar llamadas HTTP asíncronas;
  • Espere la respuesta de cada llamada a la API;
  • Utilice el patrón de Promise ;
  • Utilice Promise.All patrón para unirse a múltiples 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 dado de cadenas de consulta:

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

Para cada elemento, una nueva Promesa disparará un bloque ExecutionBlock, analizará el resultado, programará un nuevo conjunto de promesas según la matriz de resultados, es decir, una lista de userobjetos de Spotify y ejecutará la nueva llamada HTTP dentro de la ExecutionProfileBlockasíncrona.

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

NOTA Las searchAPI de Spotify recientes requerirán que se especifique un token de acceso en los encabezados de 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 ampliamente esta solución here .


La pregunta era:

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

que PUEDE interpretarse como:

¿Cómo hacer que el código asíncrono parezca sincrónico ?

La solución será evitar las devoluciones de llamada y utilizar una combinación de promesas y async / await .

Me gustaría dar un ejemplo para una solicitud Ajax.

(Aunque se puede escribir en Javascript, prefiero escribirlo en Python y compilarlo en Javascript usando Transcrypt . Será lo suficientemente claro).

Primero habilitamos el uso de JQuery, para que $esté disponible como S:

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

Defina una función que devuelva una Promesa , en este caso una llamada Ajax:

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

Usa el código asíncrono como si fuera síncrono :

async def readALot():
    try:
        result1 = await read("url_1")
        result2 = await read("url_2")
    except Exception:
        console.warn("Reading a lot failed")

Puede usar esta biblioteca personalizada (escrita con Promise) para hacer una llamada remota.

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

Ejemplo de uso simple:

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

Si bien las promesas y las devoluciones de llamadas funcionan bien en muchas situaciones, es un dolor en la parte posterior expresar algo como:

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

Terminarías atravesando async1; Compruebe si nameestá indefinido 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.

Fibers Ayuda a resolver el problema.

var Fiber = require('fibers')

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

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

Puedes ver el proyecto here .


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


En lugar de lanzarle un código, hay dos conceptos que son clave para entender cómo JS maneja las devoluciones de llamada y la asincronía. (¿Es eso una palabra?)

El modelo de bucle y concurrencia del evento

Hay tres cosas que debes tener en cuenta; La cola; el bucle de eventos y la pila

En términos amplios y simplistas, el bucle de eventos es como el administrador de proyectos, escucha constantemente cualquier función que desee ejecutar y se comunica entre la cola y la pila.

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

Una vez que recibe un mensaje para ejecutar algo, lo agrega a la cola. La cola es la lista de cosas que están esperando para ejecutarse (como su solicitud AJAX). imagínalo así:

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

Cuando uno de estos mensajes va a ejecutarse, aparece el mensaje de la cola y se crea una pila, la pila es todo lo que JS necesita ejecutar para ejecutar las instrucciones en el mensaje. Así que en nuestro ejemplo se le dice que llamefoobarFunc

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

Por lo tanto, cualquier cosa que foobarFunc necesite ejecutar (en nuestro caso anotherFunction) será empujada a la pila. ejecutado, y luego olvidado: el bucle de eventos se moverá a la siguiente cosa en la cola (o escuchará los mensajes)

La clave aquí es el orden de ejecución. Es decir

Cuando algo va a correr

Cuando realiza una llamada utilizando AJAX a una parte externa o ejecuta cualquier código asíncrono (por ejemplo, un setTimeout), Javascript depende de una respuesta antes de que pueda continuar.

La gran pregunta es ¿cuándo obtendrá la respuesta? La respuesta es que no lo sabemos, por lo que el ciclo de eventos está esperando a que el mensaje diga "hey run me". Si JS simplemente esperaba ese mensaje de forma síncrona, su aplicación se congelaría y apestaría. Por lo tanto, JS continúa ejecutando el siguiente elemento en la cola mientras espera que el mensaje se agregue nuevamente a la cola.

Es por eso que con la funcionalidad asíncrona usamos cosas llamadas devoluciones de llamada . Es un poco como una then() literalmente. Como en, prometo devolver algo en algún momento jQuery usa devoluciones de llamada específicas llamadas ( deffered.done deffered.faily deffered.alwaysentre otras). Puedes verlos todos here

Entonces, lo que debe hacer es pasar una función que se promete ejecutar en algún momento con los datos que se le pasan.

Debido a que la devolución de llamada no se ejecuta inmediatamente, pero en un momento posterior es importante pasar la referencia a la función, no a la función que se ejecuta. asi que

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

así que la mayoría de las veces (pero no siempre), pasará por foonofoo()

Esperemos que tenga algún sentido. Cuando te encuentras con cosas como esta que parecen confusas, te recomiendo que leas la documentación por completo para al menos entenderla. Te hará un desarrollador mucho mejor.


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

Es decir:

function handleData( responseData ) {

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

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

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


Este es uno de los lugares en los que las dos formas de enlace de datos que se utilizan en muchos nuevos marcos de JavaScript funcionarán en gran medida para usted ...

Entonces, si está utilizando Angular, React o cualquier otro marco que haga el enlace de datos de dos maneras, este problema simplemente se solucionará para usted, por lo que, en pocas palabras, su resultado se encuentra undefineden la primera etapa, por lo que lo tiene result = undefinedantes de recibir 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 hizo en esta pregunta?

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

Por ejemplo, en su caso, 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(); //call happens here
});

Para obtener más información, estudie sobre promesas y observables, que son formas más nuevas de hacer estas cosas asíncronas.


La respuesta corta es que tienes que 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
});

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

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

            successCallback(response);
        }
    });
}

function successCallback(responseObj){
    // Do something like read the response and show data
    alert(JSON.stringify(responseObj)); // Only applicable to 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);    
}); 

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

Si la función subyacente es promisificada

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

function synchronousCode() {

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

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

Si la función subyacente no es promisificada.

Paso 1. Ajuste la función con devolución de llamada en el envoltorio compatible con nsynjs (si tiene una versión promisificada, puede omitir esta prueba)

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 la lógica síncrona en función:

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

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

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 aquí: https://github.com/amaksr/nsynjs/tree/master/examples


Por supuesto, hay muchos enfoques como la solicitud sincrónica, la promesa, pero desde mi experiencia, creo que debería usar el enfoque de devolución de llamada. Es natural el comportamiento asíncrono de Javascript. Entonces, tu fragmento de código puede ser reescrito un poco diferente:

function foo() {
    var result;

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

    return result;
}

function myCallback(response) {
    // Does something.
}




ecmascript-2017