¿Cómo devuelvo la respuesta de una callback asincrónica?
Tengo una función foo que hace una solicitud de Ajax. ¿Cómo puedo devolver la respuesta de foo?
Intenté devolver el valor de la devolución de success callback, así como asignar la respuesta a una variable local dentro de la función y devolver esa, pero ninguna de esas formas realmente devuelve la respuesta
Funciones: asynchronous y xmlhttprequest
Adjunto aquí el detalle de la función foo
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`.
Etiquetado:
Accede o Regístrate para comentar.
Comentarios
El problema
La A en Ajax significa asíncrono. Eso significa que enviar la solicitud (o más bien recibir la respuesta) se elimina del flujo de ejecución normal. En su ejemplo, $ .ajax regresa inmediatamente y la siguiente instrucción, return result ;, se ejecuta antes de que se llamara a la función que pasó como devolución de llamada exitosa.
Aquí hay una analogía que, con suerte, hace más clara la diferencia entre flujo sincrónico y asincrónico:
Sincrónico
Imagine que hace una llamada telefónica a un amigo y le pide que busque algo por usted. Aunque puede llevar un tiempo, espera en el teléfono y mira fijamente al espacio, hasta que su amigo le dé la respuesta que necesitaba.
Lo mismo sucede cuando realiza una llamada de función que contiene el código "normal":
Aunque findItem puede tardar mucho tiempo en ejecutarse, cualquier código que venga después de var item = findItem (); tiene que esperar hasta que la función devuelva el resultado.
Asincrónico
Llamas a tu amigo nuevamente por la misma razón. Pero esta vez le dices que tienes prisa y que debería llamarte de nuevo a tu teléfono móvil. Cuelgas, sales de casa y haces lo que planeaste 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 haces una solicitud de Ajax.
En lugar de esperar la respuesta, la ejecución continúa inmediatamente y la declaración después de que se ejecuta la llamada Ajax. Para obtener la respuesta eventualmente, usted proporciona una función a la que se llamará una vez que se recibió la respuesta, una devolución de llamada (¿nota algo? ¿Devolver llamada?). Cualquier declaración posterior a esa llamada se ejecuta antes de que se llame la devolución de llamada.
Solución(es)
¡Abrace la naturaleza asincrónica de JavaScript! Si bien ciertas operaciones asincrónicas proporcionan contrapartidas sincrónicas (también lo hace "Ajax"), generalmente se desaconseja usarlas, especialmente en un contexto de navegador.
¿Por qué es malo lo que preguntas?
JavaScript se ejecuta en el subproceso de interfaz de usuario del navegador y cualquier proceso de ejecución prolongada bloqueará la interfaz de usuario, por lo que no responderá. Además, hay un límite superior en el tiempo de ejecución de JavaScript y el navegador le preguntará al usuario si continuará la ejecución o no.
Todo esto es una experiencia de usuario realmente mala. El usuario no podrá saber si todo funciona bien o no. Además, el efecto será peor para los usuarios con una conexión lenta.
A continuación, veremos tres soluciones diferentes que se están construyendo una encima de la otra:
Los tres están disponibles en los navegadores actuales y en el nodo 7+.
Te dejo aquí las diferentes soluciones:
ES2017 +: Promesas con asíncrono / espera
La versión ECMAScript lanzada en 2017 introdujo soporte de nivel de sintaxis para funciones asincrónicas. Con la ayuda de async y wait, puede escribir asincrónico en un "estilo sincrónico". El código sigue siendo asíncrono, pero es más fácil de leer / comprender.
async / await se basa en promesas: una función asincrónica siempre devuelve una promesa. esperar "desenvuelve" una promesa y resulta en el valor con el que se resolvió la promesa o arroja un error si la promesa fue rechazada.
Importante: Solo puede usar wait dentro de una función asíncrona. En este momento, la espera de nivel superior aún no es compatible, por lo que es posible que deba realizar una IIFE asincrónica (expresión de función invocada inmediatamente) para iniciar un contexto asincrónico.
Puede leer más sobre async y esperar en MDN.
Aquí hay un ejemplo que se basa en el retraso anterior:
Las versiones actuales de navegador y nodo admiten async / await. También puede admitir entornos más antiguos transformando su código a ES5 con la ayuda de regenerator (o herramientas que usan regenerator, como Babel).
Dejar que las funciones acepten callbacks
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 asincrónico, la devolución de llamada se llamará siempre que se realice el proceso asincrónico. Por lo general, el resultado se pasa a la devolución de llamada.
En el ejemplo de la pregunta, puede hacer que foo acepte una callback y usarla como callback exitosa. Así que esto:
se convierte en:
Aquí definimos la función "inline" pero puede pasar cualquier referencia de función:
foo se define de la siguiente manera:
Callback se referirá a la función que pasamos a foo cuando la llamamos y simplemente la pasamos a success. Es decir. una vez que la solicitud de Ajax es exitosa, $ .ajax llamará a la callback y pasará la respuesta a la callback (a la que se puede hacer referencia con el resultado, ya que así es como definimos la devolución de llamada).
También puede procesar la respuesta antes de pasarla a la callback:
Es más fácil escribir código usando devoluciones de llamada de lo que parece. Después de todo, JavaScript en el navegador depende en gran medida de los eventos (eventos DOM). Recibir la respuesta de Ajax no es más que un evento.
Pueden surgir dificultades cuando tiene que trabajar con código de terceros, pero la mayoría de los problemas se pueden resolver simplemente pensando en el flujo de la aplicación.
ES2015 +: Promesas con then()
La API Promises es una nueva característica de ECMAScript 6 (ES2015), pero ya tiene un buen soporte de navegador. También hay muchas bibliotecas que implementan la API Promises estándar y proporcionan métodos adicionales para facilitar el uso y la composición de funciones asincrónicas (por ejemplo, bluebird).
Las promesas son contenedores para valores futuros. Cuando la promesa recibe el valor (se resuelve) o cuando se cancela (rechaza), notifica a todos sus "oyentes" que desean acceder a este valor.
La ventaja sobre las callbacks simples es que le permiten desacoplar su código y son más fáciles de componer.
Aquí hay un ejemplo simple de uso de una promesa:
Aplicado a nuestra llamada Ajax podríamos usar promesas como esta:
Describir todas las ventajas que promete ofrecer está más allá del alcance de esta respuesta, pero si escribe un código nuevo, debería 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: los objetos diferidos de jQuery
Los objetos diferidos son la implementación personalizada de promesas de jQuery (antes de que la API Promise se estandarizara). Se comportan casi como promesas pero exponen una API ligeramente diferente.
Cada método Ajax de jQuery ya devuelve un "objeto diferido" (en realidad una promesa de un objeto diferido) que puede devolver de su función:
Nota al margen: Promesas Gotchas
Tenga en cuenta que las promesas y los objetos diferidos son solo contenedores para un valor futuro, no son el valor en sí. Por ejemplo, suponga que tiene lo siguiente:
Este código no comprende los problemas de asincronía anteriores. Específicamente, $ .ajax () no congela el código mientras revisa la página '/ contraseña' en su servidor: envía una solicitud al servidor y mientras espera, devuelve inmediatamente un objeto jQuery Ajax diferido, no la respuesta de el servidor. Eso significa que la declaración "if" siempre obtendrá este objeto diferido, trátelo como "verdadero" y proceda como si el usuario hubiera iniciado sesión. No es bueno.
Pero la solución es fácil:
No recomendado: llamadas síncronas "Ajax"
Como mencioné, algunas (!) Operaciones asincrónicas tienen contrapartidas sincrónicas. No recomiendo su uso, pero para completar, así es como realizaría una llamada síncrona:
Sin jQuery
Si usa directamente un objeto "XMLHTTPRequest", pase "false" como tercer argumento a .open.
jQuery
Si usa jQuery, puede establecer la opción "asíncrono" en "falso". Tenga en cuenta que esta opción está en desuso desde jQuery 1.8. Entonces puede usar una "callback exitosa" o acceder a la propiedad "responseText" del objeto jqXHR:
Si usa cualquier otro método jQuery Ajax, como $ .get, $ .getJSON, etc., debe cambiarlo a $ .ajax (ya que solo puede pasar los parámetros de configuración a $ .ajax).
¡Aviso! No es posible realizar una solicitud JSONP sincrónica. JSONP, por su propia naturaleza, siempre es asíncrono (una razón más para ni siquiera considerar esta opción).