una termine sincrono sincronas que programacion funciones funcion esperar ejecucion controlar carga asíncrono asincrona javascript ajax asynchronous ecmascript-6 ecmascript-2017

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



javascript sincrono (30)

→ 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).

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


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 estás usando promesas, esta respuesta es para ti.

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

Su código debe ser algo 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 en el frontend o en el backend.

El tema central

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

Cada vez que llama a un método que devuelve una promesa, los controladores then se ejecutan de forma asíncrona, es decir, después del código debajo de ellos que no está en un controlador .then .

Esto significa que cuando está devolviendo data el controlador then que ha definido no se ejecutó todavía. A su vez, esto 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 los data undefined está undefined ya que la parte de data = 5 aún no se ha ejecutado. Es probable que se ejecute en un segundo, pero en ese momento es irrelevante para el valor devuelto.

Como la operación aún no se realizó (AJAX, llamada de servidor, E / S, 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 de forma activa , indicando a su programa qué hacer cuando se complete el cálculo. Las promesas lo habilitan activamente al ser de naturaleza temporal (sensible al tiempo).

Rápido resumen de las promesas

Una promesa es un valor en el tiempo . Las promesas tienen estado, comienzan como pendientes sin valor y pueden conformarse con:

  • Cumplido lo que significa que el cálculo completado 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 los controladores a las promesas para extraer su valor y manejar los errores. then manejadores permiten el chaining de llamadas. Las promesas se crean utilizando las API que las devuelven . Por ejemplo, la fetch reemplazo de AJAX más moderna o las promesas de devolución de $.get jQuery.

Cuando llamamos a .then a una promesa y devolvemos algo de ella, obtenemos una promesa por el valor procesado . Si devolvemos otra promesa, obtendremos cosas increíbles, pero sostengamos nuestros caballos.

Con promesas

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

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

Ahora, después de convertir setTimeout para usar promesas, podemos usarlo para hacer que cuente:

function delay(ms){ // takes amount of milliseconds // returns a new promise return new Promise(function(resolve, reject){ setTimeout(function(){ // when the time is up resolve(); // change the promise to the fulfilled state }, ms); }); } function getFive(){ // we''re RETURNING the promise, remember, a promise is a wrapper over our value return delay(100).then(function(){ // when the promise is ready return 5; // return the value 5, promises are all about return values }) } // we _have_ to wrap it like this in the call site, we can''t access the plain value getFive().then(function(five){ document.body.innerHTML = five; });

Básicamente, en lugar de devolver un valor que no se puede hacer debido a la modelo de concurrencia - estamos devolviendo un envoltorio para un valor que podemos desenvolver con then. Es como una caja con la que puedes abrir then.

Aplicando esto

Esto es igual para su llamada API original, puede:

function foo() { // RETURN the promise return fetch("/echo/json").then(function(response){ return response.json(); // process it inside the `then` }); } foo().then(function(response){ // access the value inside the `then` })

Así que esto funciona igual de bien. Hemos aprendido que no podemos devolver valores de llamadas ya 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 generators 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/node/io only yield 1; yield 2; while(true) yield 3; }

Es una función que devuelve un iterador sobre la secuencia 1,2,3,3,3,3,....que 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 un tanto complicado pero muy poderoso nos permite escribir código asíncrono de una manera sincrónica. Hay varios "corredores" que hacen esto por usted, escribir uno es unas pocas líneas de código, pero está fuera del alcance de esta respuesta. Estaré usando Bluebird''s Promise.coroutineaquí, pero hay otros wrappers como coo Q.async.

var foo = coroutine(function*(){ var data = yield fetch("/echo/json"); // notice the yield // code here only executes _after_ the request is done return data.json(); // data is defined });

Este método devuelve una promesa en sí misma, que podemos consumir de otras coroutinas. Por ejemplo:

var main = coroutine(function*(){ var bar = yield foo(); // wait our earlier coroutine, it returns a promise // server call done here, code below executes when done var baz = yield fetch("/api/users/"+bar.userid); // depends on foo''s result console.log(baz); // runs after both requests done }); main();

ES2016 (ES7)

En ES7, esto está aún más estandarizado, hay varias propuestas en este momento, pero en todas ellas puedes awaitprometer. Esto es solo "azúcar" (mejor sintaxis) para la propuesta ES6 anterior al agregar las palabras clave asyncy await. Haciendo el ejemplo anterior:

async function foo(){ var data = await fetch("/echo/json"); // notice the await // code here only executes _after_ the request is done return data.json(); // data is defined }

Todavía devuelve una promesa igual :)


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


Angular1

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

Here dice,

Las promesas se pueden utilizar para anular funciones asíncronas y permiten encadenar múltiples funciones.

Puedes encontrar una buena explicación here también.

Ejemplo encontrado en los docs mencionados 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.

Angular2 y posteriores

En Angular2con mirar el siguiente ejemplo, pero se recommended su uso Observablescon Angular2.

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

}

Puedes consumir eso de esta manera,

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

Vea la publicación original aquí. Pero Typescript no es compatible con las promesas nativas de es6 , si desea usarlo, es posible que necesite un complemento para eso.

Además aquí está la spec promesas aquí definida.


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


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 cuando regresa res.data.value). Así que 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/


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.


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://.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 haz 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 un compilador de cierre para generar ECMAScript 5.

La devolución de llamada ...argsse envuelve en una matriz y se desestructura cuando las lee para que el patrón pueda hacer frente a las devoluciones de llamada que tienen varios argumentos. Por ejemplo con el nodo fs :

const [err, data] = yield fs.readFile(filePath, "utf-8", callback);


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


Otro método para devolver un valor desde 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 utilizando 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 este enfoque mucho. Me interesaría saber qué tan bien funciona este enfoque cuando se trata de transferir el resultado a través de módulos consecutivos.


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 .


Usando ES2017 deberías tener esto como la declaración de función.

async function foo() { var response = await $.ajax({url: ''...''}) return response; }

Y ejecutándolo así.

(async function() { try { var result = await foo() console.log(result) } catch (e) {} })()

O la sintaxis de Promise

foo().then(response => { console.log(response) }).catch(error => { console.log(error) })


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.


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

Comencemos con una simple función de JavaScript:

function foo(){ // do something return ''wohoo''; } let bar = foo(); // bar is ''wohoo'' here

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

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

function foo(){ setTimeout( ()=>{ return ''wohoo''; }, 1000 ) } let bar = foo() // bar is undefined here

¡Ahí tienes, ese retraso acaba de romper la funcionalidad que esperábamos! Pero, ¿qué pasó exactamente? Bueno, en realidad es bastante lógico si miras el código. la función foo(), al ejecutarse, no devuelve nada (por lo tanto, el valor devuelto es undefined), pero inicia un temporizador, que ejecuta una función después de 1s para devolver ''wohoo''. Pero como puede ver, el valor que se asigna a la barra es el material devuelto inmediatamente de foo (), no otra cosa que venga después.

Entonces, ¿cómo abordamos este problema?

Pidamos a nuestra función una PROMESA . La promesa realmente tiene que ver con lo que significa: significa que la función le garantiza proporcionar cualquier salida que obtenga en el futuro. Así que vamos a verlo 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 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 asíncronas como las llamadas basadas en ajax, etc., puede utilizar una promesa para resolveel valor (que pretende devolver). Por lo tanto, en resumen, usted resuelve el valor en lugar de regresar , en funciones asíncronas.

ACTUALIZACIÓN (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 cumplan las promesas , antes de pasar a la siguiente línea de código. Sigue siendo solo promisesbajo el capó, pero con un enfoque sintáctico diferente. Para aclarar las cosas, puede encontrar una comparación a continuación:

entonces / versión de captura:

function fetchUsers(){ let users = []; getUsers() .then(_users => users = _users) .catch(err =>{ throw err }) return users; }

versión async / await:

async function fetchUsers(){ try{ let users = await getUsers() return users; } catch(err){ throw err; } }


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 mayoría de las respuestas aquí ofrecen 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 doSomethingAsyncno se han ejecutado aún cuando intenta utilizar los resultados.

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

Paralela

Puede iniciarlos todos y hacer un seguimiento de cuántas devoluciones de llamadas está esperando, 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:", 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 solo usar results.length === theArray.length, pero eso nos deja abiertos a la posibilidad de que theArrayse modifique mientras las llamadas son sobresalientes ...)

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

Pero, ¿qué pasa si necesita devolver esos resultados de una función? Como han señalado las otras respuestas, no se puede; debe tener su función aceptar y llamar a una devolución de llamada (o devolver una then() ). Aquí hay una versión de devolución de llamada:

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

Ejemplo:

function doSomethingWith(theArray, callback) { var results = []; var expecting = theArray.length; theArray.forEach(function(entry, index) { doSomethingAsync(entry, function(result) { results[index] = result; if (--expecting === 0) { // Done! callback(results); } }); }); } doSomethingWith([1, 2, 3], function(results) { console.log("Results:", results); }); function doSomethingAsync(value, callback) { console.log("Starting async operation for " + value); setTimeout(function() { console.log("Completing async operation for " + value); callback(value * 2); }, Math.floor(Math.random() * 200)); }

.as-console-wrapper { max-height: 100% !important; }

O aquí hay una versión que devuelve una Promiseen su lugar:

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

Por supuesto, si doSomethingAsyncnos 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:", 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 una envoltura para doSomethingAsynceso, devolver una promesa, y luego hacer lo siguiente ...)

Si doSomethingAsyncte da una then() , puedes usar Promise.all:

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

Ejemplo:

function doSomethingWith(theArray) { return Promise.all(theArray.map(function(entry) { return doSomethingAsync(entry, function(result) { results.push(result); }); })); } doSomethingWith([1, 2, 3]).then(function(results) { console.log("Results:", results); }); function doSomethingAsync(value) { console.log("Starting async operation for " + value); return new Promise(function(resolve) { setTimeout(function() { console.log("Completing async operation for " + value); resolve(value * 2); }, Math.floor(Math.random() * 200)); }); }

.as-console-wrapper { max-height: 100% !important; }

Tenga en cuenta que Promise.allresuelve su promesa con una serie de resultados de todas las promesas que le hace cuando se resuelven, o que rechaza su promesa cuando la primera de las promesas que usted da es rechazada.

Serie

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

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

(Ya que estamos haciendo el trabajo en serie, podemos usarlo results.push(result)ya que sabemos que no obtendremos resultados fuera de orden. En lo anterior, podríamos haberlo utilizado 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:", results); }); function doSomethingAsync(value, callback) { console.log("Starting async operation for " + value); setTimeout(function() { console.log("Completing async operation for " + value); callback(value * 2); }, Math.floor(Math.random() * 200)); }

.as-console-wrapper { max-height: 100% !important; }

(O, nuevamente, construir una envoltura para doSomethingAsynceso te da una promesa y haz lo siguiente ...)

Si doSomethingAsyncle da una Promesa, si puede usar la sintaxis de ES2017 + (quizás con un transpiler como Babel ), puede usar una async/await 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:", 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 en el patrón de " Reducción de promesa" (esto es más complejo que la reducción de Promesa habitual porque no estamos pasando el resultado de uno a otro, sino que recogiendo sus resultados en una matriz):

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

Ejemplo:

function doSomethingWith(theArray) { return theArray.reduce(function(p, entry) { return p.then(function(results) { return doSomethingAsync(entry).then(function(result) { results.push(result); return results; }); }); }, Promise.resolve([])); } doSomethingWith([1, 2, 3]).then(function(results) { console.log("Results:", results); }); function doSomethingAsync(value) { console.log("Starting async operation for " + value); return new Promise(function(resolve) { setTimeout(function() { console.log("Completing async operation for " + value); resolve(value * 2); }, Math.floor(Math.random() * 200)); }); }

.as-console-wrapper { max-height: 100% !important; }

... lo 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:", 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; }


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


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 ello: "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 preparadas 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 "asincronicidad". En nuestro mundo cada vez más en red, el caso más común de asinquonicidad es esperar 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é ahora, porque aún no está aquí. Tienes que esperar a que llegue antes de ponerlo en tu café. En otras palabras, lo siguiente no funcionará:

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

Porque JS no tiene forma de saber que necesita esperar para order_milkterminar antes de ejecutarse put_in_coffee. En otras palabras, no sabe que order_milkes asíncrono, es algo que no producirá leche hasta un futuro. JS y otros lenguajes declarativos, ejecutan una declaración tras otra sin esperar.

El enfoque clásico de JS para este problema, aprovechando el hecho de que JS 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 complete 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_milkcomienza, ordena la leche, luego, cuando y solo 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 su lugar, las funciones deben nostar reportes de sus resultados llamando a un callback dado como un parámetro. Además, este enfoque puede volverse rápidamente difícil de manejar cuando se trata de secuencias más largas de eventos. Por ejemplo, digamos que quiero esperar a que la leche se ponga en el café, y luego, y solo entonces, realizar un tercer paso, a saber, tomar el café. Termino necesitando escribir algo como esto:

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

donde paso a put_in_coffeela leche para poner en ella, y también a la acción ( drink_coffee) para ejecutar una vez que se ha puesto la leche. Tal 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 tal vez nunca suceda. Las promesas tienen un método único, denominado then, al que se pasa una acción para que se ejecute cuando se cumpla el resultado que representa la promesa.

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

order_milk() . then(put_in_coffee)

Una ventaja de esto es que podemos juntarlos para crear secuencias de ocurrencias futuras ("encadenamiento"):

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

Apliquemos 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 se añade 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 ajustar esta llamada para devolver una promesa real, o usar alguna alternativa para $.ajaxhacerlo). Ahora, si queremos cargar el archivo y esperar a que termine y luego hacer algo, simplemente podemos decir

get_data() . then(do_something)

por ejemplo,

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

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

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

La asyncpalabra clave

Pero todavía hay algo vagamente insatisfactorio acerca de tener que escribir código de una manera si es sincrónica y una forma bastante diferente si es asíncrona Para síncrono, escribimos

a(); b();

Pero si aes asíncrono, con promesas tenemos que escribir.

a() . then(b);

Arriba, dijimos que "JS no tiene forma de saber que necesita esperar a que termine la primera llamada antes de ejecutar la segunda". ¿No sería agradable si era alguna forma de saber que JS? Resulta que hay - la awaitpalabra clave, utilizada dentro de un tipo especial de función llamada función "asíncrona". Esta función es parte de la próxima versión de ES, pero ya está disponible en transpilers como Babel, dado 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); }


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


Usa una callback()función dentro del foo()éxito. Intenta 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();