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 success, 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.

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`.
5787
Felix Kling 8 ene. 2013 a las 21:06

28 respuestas

{{X0}} - & gt; coincidir el objeto o {{x1}}. Escanee a través de la cadena en busca de una coincidencia y devuelva una instancia correspondiente {{x2}}. Devuelve {{x3}}} SI NO ES POSICIÓN EN LA CADRIGA…

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

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

Como puedes ver:

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

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

Lo más simple:

this.response

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

e.target.response

Ejemplo:

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

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

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

Nada mas facil.

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

Consulte características avanzadas de XMLHttpRequest

Admitía 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 onload / oreadystatechange; de lo contrario, lo perdió.


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

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

Una vez más ... es una función muy corta, pero obtiene y 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 establezca algunos valores personalizados:

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

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

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


Como se mencionó en el comentario, el uso de error && synchronous rompe por completo el punto de la respuesta. ¿Cuál es una buena forma de usar Ajax de la manera adecuada?

Controlador de errores

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

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

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

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

Los controladores de errores pueden ser útiles si configura encabezados personalizados, establece el tipo de respuesta en búfer de matriz de blobs o lo que sea ...

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

Incluso si pasa 'fdggdgilfdghfldj' como formdata, no arrojará un error.

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

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

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

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

Solo hay this.type que está configurado como error.

¿Por qué agregar un controlador 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 ... ¿??? Así que verifiqué la carpeta donde se encuentra el HTML y había un archivo llamado 'x.xml'. Entonces, incluso si olvida la extensión de su archivo XMLHttpRequest 2 LO ENCONTRARÁ . Yo jajaja


Leer un archivo sincrónico

No hagas eso.

Si desea bloquear el navegador por un tiempo, cargue un gran archivo .txt síncrono.

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 una manera no asincrónica. (Sí, con el ciclo setTimeout ... pero en serio?)

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

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


Las funciones anteriores son para uso básico.

Si desea EXTENDER la función ...

Sí, puedes.

Estoy usando muchas API y una de las primeras funciones que integro en cada página HTML es la primera función Ajax en esta respuesta, solo con GET ...

Pero puedes hacer muchas cosas con XMLHttpRequest 2:

Hice un administrador de descargas (usando rangos en ambos lados con currículum, lector de archivos, sistema de archivos), varios convertidores de redimensionadores de imágenes usando lienzo, poblar bases de datos SQL web con imágenes base64 y mucho más ... Pero en estos casos, debe crear una función solo para eso propósito ... a veces necesitas un blob, búferes de matriz, puedes configurar 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).

380
Quentin 10 dic. 2018 a las 09:08

La mayoría de las respuestas aquí dan sugerencias útiles para cuando tiene una sola operación asíncrona, pero a veces, esto surge cuando necesita hacer una operación asincrónica para cada entrada en una matriz u otra estructura similar a una lista . La tentación es hacer esto:

// WRONG
var results = [];
theArray.forEach(function(entry) {
    doSomethingAsync(entry, function(result) {
        results.push(result);
    });
});
console.log(results); // E.g., using them, returning them, etc.

Ejemplo:

// WRONG
var theArray = [1, 2, 3];
var results = [];
theArray.forEach(function(entry) {
    doSomethingAsync(entry, function(result) {
        results.push(result);
    });
});
console.log("Results:", results); // E.g., using them, returning them, etc.

function doSomethingAsync(value, callback) {
    console.log("Starting async operation for " + value);
    setTimeout(function() {
        console.log("Completing async operation for " + value);
        callback(value * 2);
    }, Math.floor(Math.random() * 200));
}
.as-console-wrapper {
  max-height: 100% !important;
}

La razón por la que no funciona es que las devoluciones de llamada de doSomethingAsync aún no se han ejecutado cuando intenta utilizar los resultados.

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

Paralela

Puede iniciarlos todos y realizar un seguimiento de la cantidad de devoluciones de llamada que espera, y luego usar los resultados cuando haya recibido tantas devoluciones de llamada:

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

Ejemplo:

var theArray = [1, 2, 3];
var results = [];
var expecting = theArray.length;
theArray.forEach(function(entry, index) {
    doSomethingAsync(entry, function(result) {
        results[index] = result;
        if (--expecting === 0) {
            // Done!
            console.log("Results:", 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 expecting y simplemente usar results.length === theArray.length, pero eso nos deja abiertos a la posibilidad de que theArray se cambie mientras las llamadas están pendientes ...)

Observe cómo usamos el index de forEach para guardar el resultado en results en la 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 no necesariamente se completa en el orden en que se iniciaron).

Pero, ¿qué sucede si necesita devolver esos resultados de una función? Como las otras respuestas han señalado, no puedes; debe hacer que su función acepte y devuelva una llamada (o devuelva un Promesa). Aquí hay una versión de devolución de llamada:

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

Ejemplo:

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

function doSomethingAsync(value, callback) {
    console.log("Starting async operation for " + value);
    setTimeout(function() {
        console.log("Completing async operation for " + value);
        callback(value * 2);
    }, Math.floor(Math.random() * 200));
}
.as-console-wrapper {
  max-height: 100% !important;
}

O aquí hay una versión que devuelve un Promise en 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 doSomethingAsync nos pasa los errores, usaríamos reject para 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ía hacer un contenedor para doSomethingAsync que devuelva una promesa, y luego haga lo siguiente ...)

Si doSomethingAsync le da un Promesa , puede usar Promise.all:

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

Si sabe que doSomethingAsync ignorará un segundo y tercer argumento, puede pasarlo directamente a map (map llama a su devolución de llamada con tres argumentos, pero la mayoría de las personas solo usa el primero del tiempo):

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

Ejemplo:

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

Serie

¿Y si 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);
});

(Dado que estamos haciendo el trabajo en serie, podemos usar results.push(result) ya que sabemos que no obtendremos resultados fuera de orden. En lo anterior podríamos haber usado results[index] = result;, pero en algunos de los siguientes ejemplos no tenemos un índice para 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, de nuevo, construya una envoltura para doSomethingAsync que le prometa y haga lo siguiente ...)

Si doSomethingAsync le da una promesa, si puede usar la sintaxis ES2017 + (quizás con un transpilador como Babel), puede usar una función async con for-of y 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 ES2017 + (todavía), puede usar una variación en " Promise reduce " (esto es más complejo que el habitual Promise reduce porque no estamos pasando el resultado de uno a otro, sino que reunimos 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;
}

... que es menos engorroso con ES2015 + funciones de flecha:

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

Ejemplo:

function doSomethingWith(theArray) {
    return theArray.reduce((p, entry) => p.then(results => doSomethingAsync(entry).then(result => {
        results.push(result);
        return results;
    })), Promise.resolve([]));
}
doSomethingWith([1, 2, 3]).then(function(results) {
    console.log("Results:", 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;
}
140
T.J. Crowder 26 abr. 2019 a las 07:50

Está utilizando Ajax incorrectamente. La idea no es que devuelva nada, sino que entregue los datos a algo llamado función de devolución de llamada, que controla 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 desee con ellos directamente dentro de la función de éxito.

238
Peter Mortensen 21 nov. 2015 a las 14:07

Es un problema muy común que enfrentamos mientras luchamos con los 'misterios' de JavaScript. Permítanme 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 síncrona (donde cada línea de código está 'terminada con su trabajo' antes de la siguiente en secuencia), y el resultado es el mismo que se esperaba.

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

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

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

¡Ahí tienes, ese retraso simplemente rompió la funcionalidad que esperábamos! ¿Pero qué pasó exactamente? Bueno, en realidad es bastante lógico si nos fijamos en 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 asignado a la barra es el material devuelto inmediatamente por foo (), no cualquier otra cosa que venga más tarde.

Entonces, ¿cómo abordamos este problema?

Pidamos a nuestra función una PROMESA . Promise se trata realmente de 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 asincrónicas como llamadas basadas en ajax, etc., puede usar una promesa de resolve el valor (que tiene la intención de devolver). Por lo tanto, en resumen, resuelve el valor en lugar de regresar , en funciones asincrónicas.

ACTUALIZACIÓN (Promesas con asíncrono / espera)

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

Entonces / captura versión:

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

Versión asíncrona / espera:

  async function saveUsers(){
     try{
        let users = await getUsers()
        saveSomewhere(users);
     }
     catch(err){
        throw err;
     }
  }
67
Anish K. 24 sep. 2019 a las 10:40

Usando Promesa

La respuesta más perfecta a esta pregunta es usar Promise.

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

Uso

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

Pero espera...!

¡Hay un problema con el uso de promesas!

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

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

Uncaught ReferenceError: Promise is not defined

Así que decidí implementar mi propia clase Promise para ES3 a continuación compiladores js si no está definido. Simplemente agregue este código antes de su código principal y luego use Promise con seguridad.

if(typeof Promise === "undefined"){
    function _classCallCheck(instance, Constructor) {
        if (!(instance instanceof Constructor)) { 
            throw new TypeError("Cannot call a class as a function"); 
        }
    }
    var Promise = function () {
        function Promise(main) {
            var _this = this;
            _classCallCheck(this, Promise);
            this.value = undefined;
            this.callbacks = [];
            var resolve = function resolve(resolveValue) {
                _this.value = resolveValue;
                _this.triggerCallbacks();
            };
            var reject = function reject(rejectValue) {
                _this.value = rejectValue;
                _this.triggerCallbacks();
            };
            main(resolve, reject);
        }
        Promise.prototype.then = function then(cb) {
            var _this2 = this;
            var next = new Promise(function (resolve) {
                _this2.callbacks.push(function (x) {
                    return resolve(cb(x));
                });
            });
            return next;
        };
        Promise.prototype.catch = function catch_(cb) {
            var _this2 = this;
            var next = new Promise(function (reject) {
                _this2.callbacks.push(function (x) {
                    return reject(cb(x));
                });
            });
            return next;
        };
        Promise.prototype.triggerCallbacks = function triggerCallbacks() {
            var _this3 = this;
            this.callbacks.forEach(function (cb) {
                cb(_this3.value);
            });
        };
        return Promise;
    }();
}
22
Amir Forsati 28 may. 2019 a las 10:08

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

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

$.ajax({
    url: "...",
    success: callback
});
78
Pablo Matias Gomez 9 ago. 2016 a las 18:32

Responderé con un cómic horrible y dibujado a mano. La segunda imagen es la razón por la que result es undefined en el ejemplo de código.

enter image description here

208
Johannes Fahrenkrug 11 ago. 2016 a las 17:12

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

Entonces, si está utilizando Angular, React o cualquier otro marco que haga enlace de datos de dos maneras o concepto de tienda , este problema simplemente se soluciona para usted, en pocas palabras, su resultado es undefined en la primera etapa, por lo que tiene result = undefined antes de recibir los datos, luego, tan pronto como obtenga el resultado, se actualizará y se asignará a el 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 usted, por ejemplo, en promesas tenemos algunas funciones como {{X0} } o then() que se ejecutará cuando sus datos estén listos para usted, lo mismo con la función de devolución de llamada o suscripción en observable .

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

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

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

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

Para obtener más información, estudie las promesas y observables , que son nuevas formas de hacer esto de forma asíncrona.

96
Alireza 23 sep. 2019 a las 03:14

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

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

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

Para ejecutar el código anterior, haga lo siguiente:

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

Si necesita dirigirse a exploradores que no admiten ES6, puede ejecutar el código a través de Babel o closure-compiler para generar ECMAScript 5.

La devolución de llamada ...args se envuelve en una matriz y se desestructura cuando la lee para que el patrón pueda hacer frente a las devoluciones de llamada que tienen múltiples argumentos. Por ejemplo con node fs:

const [err, data] = yield fs.readFile(filePath, "utf-8", callback);
39
James 31 jul. 2018 a las 10:35

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

Se han dado varias soluciones en este hilo. Quizás la forma más fácil es pasar un objeto al método foo() 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() aún no devolverá nada útil. Sin embargo, el resultado de la llamada asincrónica ahora se almacenará en result.response.

36
David R Tribble 23 sep. 2015 a las 22:52

Si bien las promesas y las devoluciones de llamada funcionan bien en muchas situaciones, es difícil expresar algo como:

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

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

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

async1(name, async2)

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

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
}

Puede ver el proyecto aquí.

83
rohithpr 9 may. 2016 a las 13:02

Eche un vistazo a este ejemplo:

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

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

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

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

Como puede ver, getJoke está devolviendo una promesa resuelta (se resuelve al devolver res.data.value). Por lo tanto, espere hasta que se complete la solicitud $ http.get y luego se ejecute console.log (res.joke) (como un flujo asincrónico normal).

Este es el plnkr:

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

ES6 way (asíncrono - espera)

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

  getJoke().then((joke) => {
    console.log(joke);
  });
})();
104
Francisco Carmona 23 nov. 2018 a las 12:19

Aquí hay algunos enfoques para trabajar con solicitudes asincrónicas:

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

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

var App = App || {};

App = {
    getDataFromServer: function(){

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

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

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

    init: function(){

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

           // Do the operations which you wanted to do when you
           // get a response from Ajax, for example, log response.
        }, this));
    }
};
App.init();
38
Mohan Dere 22 jun. 2017 a las 09:31

Por supuesto, hay muchos enfoques como solicitud sincrónica, promesa, pero por mi experiencia creo que debe usar el enfoque de devolución de llamada. Es natural el comportamiento asincrónico de Javascript. Por lo tanto, el fragmento de código se puede reescribir un poco diferente:

function foo() {
    var result;

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

    return result;
}

function myCallback(response) {
    // Does something.
}
28
Michał Perłakowski 7 mar. 2018 a las 14:23

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

Si se promete la función subyacente

Nsynjs evaluará todas las promesas secuencialmente y colocará el resultado de la promesa en la propiedad data:

function synchronousCode() {

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

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

Si no se promete la función subyacente

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

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

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

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

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

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

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

Más ejemplos aquí: https://github.com/amaksr/nsynjs/tree/master/examples

62
amaksr 3 abr. 2019 a las 16:00

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

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);    
}); 
225
clearlight 15 ene. 2017 a las 06:17

Utilice una función callback() dentro del éxito foo(). Inténtalo 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();
35
Alex Weitz 17 nov. 2017 a las 09:15

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

Esto es bastante simple:

  • Devolver una promesa
  • Utilice el 'aguarde', que le indicará JavaScript para esperar la promesa de resolverse en un valor (como la respuesta HTTP)
  • Agregue la palabra clave 'async' al padre función

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

(async function(){

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

})()

esperar es compatible con todos los navegadores actuales y el nodo 8

73
mikemaccana 1 oct. 2018 a las 10:06

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

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

Ejemplo de uso simple:

$http({
    method: 'get',
    url: 'google.com'
}).then(function(response) {
    console.log(response);
}, function(error) {
    console.log(error)
});
65
Peter Mortensen 17 dic. 2016 a las 10:59

La pregunta fue:

¿Cómo devuelvo la respuesta de una llamada asincrónica?

Que PUEDE interpretarse como:

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

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 de Ajax.

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

Primero habilitemos el uso de JQuery, para tener $ 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()

Use 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")
27
Pieter Jan Bonestroo 24 may. 2018 a las 08:36

Usando ES2017 debe 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)

})
16
Fernando Carvajal 24 ene. 2018 a las 06:18

Js es un único subproceso.

El navegador se puede dividir en tres partes:

1) Bucle de eventos

2) API web

3) Cola de eventos

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

Ahora pensemos que empujamos dos funciones en una cola, una es para obtener datos del servidor y otra utiliza esos datos. Primero empujamos la función serverRequest () en la cola y luego utilizamos la función Data (). La función serverRequest entra en el bucle de eventos y realiza una llamada al servidor, ya que nunca sabemos cuánto tiempo llevará obtener datos del servidor, por lo que se espera que este proceso tome tiempo y, por lo tanto, ocupamos nuestro bucle de eventos, colgando nuestra página, ahí es donde Web API entra en el rol, toma esta función del bucle de eventos y trata con el servidor haciendo que el bucle de eventos sea gratuito para que podamos ejecutar la siguiente función desde la cola. La siguiente función en la cola es utiliseData () que va 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 llama llamada asíncrona, es decir, podemos hacer algo más hasta que obtengamos datos)

Supongamos que nuestra función serverRequest () tiene una declaración de retorno en un código, cuando recuperamos los datos de la API web del servidor la empujará a la cola al final de la cola. Como se empuja al final de la cola, no podemos utilizar sus datos ya que no queda ninguna función en nuestra cola para utilizar estos datos. Por lo tanto, no es posible devolver algo de Async Call.

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

Una imagen de una de las respuestas aquí, explica correctamente el uso de la devolución de llamada ... Le damos a nuestra función (función que utiliza los datos devueltos por el servidor) al servidor que llama a la función.

CallBack

 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 llamadas asíncronas (@Felix Kling Answer on Top) https://stackoverflow.com/a/14220323/7579856

60
Aniket Jha 16 mar. 2018 a las 16:48

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

Aquí está un ejemplo de lo mismo:

var async = require("async");

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

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

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

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

92
Peter Mortensen 17 dic. 2016 a las 12:55

Angular1

Para las personas que usan AngularJS, pueden manejar esta situación usando Promises.

Aquí dice:

Las promesas se pueden usar para anular funciones asincrónicas y le permiten encadenar múltiples funciones.

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

Ejemplo encontrado en docs mencionado 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 posterior

En Angular2 con el siguiente ejemplo, pero es recomendado para usar Observables con 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 original publique aquí. Pero Typescript no es compatible con native es6 Promises, si desea usarlo, es posible que necesite un complemento para eso.

Además, aquí están las promesas spec definen aquí.

151
Maleen Abewardana 6 jul. 2017 a las 04:45

En lugar de arrojarle código, hay 2 conceptos que son clave para comprender cómo JS maneja las devoluciones de llamadas y la asincronía. (¿es eso una palabra?)

El modelo de bucle de eventos y concurrencia

Hay tres cosas que debe 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 del proyecto, está constantemente escuchando cualquier función que quiera ejecutarse 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 se va a ejecutar, saca el mensaje de la cola y crea una pila, la pila es todo lo que JS necesita ejecutar para realizar la instrucción en el mensaje. Entonces, en nuestro ejemplo, se le dice que llame a foobarFunc

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

Entonces, cualquier cosa que foobarFunc necesite ejecutar (en nuestro caso anotherFunction) será empujada a la pila. ejecutado y luego olvidado: el bucle de eventos pasará al siguiente elemento de la cola (o escuchará mensajes)

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

CUÁNDO se ejecutará algo

Cuando realiza una llamada usando AJAX a una parte externa o ejecuta cualquier código asincrónico (un setTimeout, por ejemplo), 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 bucle de eventos está esperando que ese mensaje diga "oye, corre conmigo". Si JS solo esperara ese mensaje sincrónicamente, su aplicación se congelaría y apestaría. Entonces 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 asincrónica usamos cosas llamadas devoluciones de llamada . Es como una promesa literalmente. Como en prometo devolver algo en algún momento jQuery utiliza devoluciones de llamada específicas llamadas deffered.done deffered.fail y deffered.always (entre otras). Puedes verlos todos aquí

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 una devolución de llamada no se ejecuta de inmediato, pero más adelante es importante pasar la referencia a la función que no se ejecutó. entonces

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

Así que la mayoría de las veces (pero no siempre) pasarás foo no foo()

Esperemos que tenga sentido. Cuando encuentre cosas como esta que parecen confusas, le recomiendo leer la documentación completa para al menos comprenderla. Te convertirá en un desarrollador mucho mejor.

17
Matthew Brent 4 may. 2018 a las 15:56

Nos encontramos en un universo que parece progresar a lo largo de una dimensión que llamamos "tiempo". Realmente no entendemos qué hora es, 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 "asincronía". En nuestro mundo cada vez más conectado en red, el caso más común de asincronía es esperar que algún sistema remoto responda a alguna solicitud.

Considera un ejemplo. Llamas al lechero y pides un poco de leche. Cuando llegue, querrás ponerlo en tu café. No puedes poner la leche en tu café en este momento, porque todavía 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);

Debido a que JS no tiene forma de saber que necesita esperar a que order_milk termine antes de ejecutar put_in_coffee. En otras palabras, no sabe que order_milk es asíncrono , es algo que no va a producir leche hasta algún momento 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 asincrónica, que luego invocará cuando se haya completado su tarea en algún momento en el futuro. Ese es el enfoque de "devolución de llamada". Se parece a esto:

order_milk(put_in_coffee);

order_milk comienza, 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 cambio, las funciones no deben informar sus resultados llamando a una devolución de llamada dada como un parámetro. Además, este enfoque puede volverse difícil de manejar rápidamente cuando se trata de secuencias de eventos más largas. Por ejemplo, digamos que quiero esperar a que se ponga la leche en el café y luego, y solo entonces, realizar un tercer paso, es decir, beber el café. Termino necesitando escribir algo como esto:

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

Donde estoy pasando a put_in_coffee tanto la leche para poner en ella, como también 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 una "promesa", que es un tipo particular de valor que representa un futuro o asíncrono de algún tipo. Puede representar algo que ya sucedió, o que va a suceder en el futuro, o puede que nunca suceda. Las promesas tienen un único método, llamado then, al que le pasa una acción para que se ejecute cuando el resultado que representa la promesa se ha cumplido.

En el caso de nuestra leche y café, diseñamos order_milk para devolver una promesa para la llegada de la leche, luego especificamos put_in_coffee como una acción then, de la siguiente manera:

order_milk() . then(put_in_coffee)

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

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

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

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

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

get_data() . then(do_something)

Por ejemplo,

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

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

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

La palabra clave async

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

a();
b();

Pero si a es asíncrono, con promesas tenemos que escribir

a() . then(b);

Arriba, dijimos, "JS no tiene forma de saber que necesita esperar a que termine la primera llamada antes de ejecutar la segunda". ¿No sería bueno si hubiera alguna forma de decirle a JS eso? Resulta que la palabra clave await se usa dentro de un tipo especial de función llamada función "asíncrona". Esta característica es parte de la próxima versión de ES pero ya está disponible en transpiladores como Babel con los ajustes preestablecidos correctos. Esto nos permite simplemente escribir

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

En su caso, podría escribir algo como

async function foo() {
  data = await get_data();
  console.log(data);
}
33
4 revs, 3 users 92%user663031 10 dic. 2018 a las 05:52

El siguiente ejemplo que he escrito muestra cómo

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

Este ejemplo de trabajo es independiente. Definirá un objeto de solicitud simple que utiliza el objeto window XMLHttpRequest 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 {{ X0}} objetos 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 activará un bloque - ExecutionBlock, analizará el resultado, programará un nuevo conjunto de promesas basadas en la matriz de resultados, es decir, una lista de objetos Spotify user y ejecutará la nueva llamada HTTP dentro del ExecutionProfileBlock de forma asincrónica.

A continuación, puede ver una estructura Promise anidada, que le permite generar varias llamadas HTTP anidadas completamente asincrónicas y unir los resultados de cada subconjunto de llamadas a través de Promise.all.

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

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

Por lo tanto, para ejecutar el siguiente ejemplo, debe colocar el 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 aquí.

77
loretoparisi 28 nov. 2018 a las 16:42

Si no usa jQuery en su código, esta respuesta es para usted

Su código debería ser algo similar a esto:

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

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

Felix Kling hizo un buen trabajo escribiendo una respuesta para las personas que usan jQuery para AJAX, he decidido proporcionar una alternativa para las personas que no lo son.

(Nota: para aquellos que usan la nueva API fetch, Angular o promesas, he agregado otra respuesta a continuación)


A lo que te enfrentas

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

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, .send regresa de inmediato y la siguiente instrucción, return result;, se ejecuta antes de que se llamara a la función que pasó como success.

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

Aquí hay una analogía simple

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

(Violín)

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

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

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

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

Esto se llama CPS. Básicamente, estamos pasando getFive una acción para realizar cuando se completa, le estamos diciendo a nuestro código cómo reaccionar cuando se completa un evento (como nuestra llamada AJAX, o en este caso el tiempo de espera).

El uso sería:

getFive(onComplete);

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

Soluciones posibles

Básicamente, hay dos formas de resolver esto:

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

1. AJAX síncrona - ¡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 sincrónicas y asincrónicas. Sin embargo, en general, las solicitudes asíncronas deben preferirse a las solicitudes síncronas por razones de rendimiento.

En resumen, las solicitudes sincrónicas bloquean la ejecución del código ... ... esto puede causar serios problemas ...

Si tiene para hacerlo, puede pasar una marca: Aquí se explica 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. Código de reestructuración

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

Así que:

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

Se convierte en:

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

Aquí pasamos una función anónima, pero podríamos pasar fácilmente una referencia a una función existente, haciendo que se vea así:

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

Para obtener más detalles sobre cómo se hace este tipo de diseño de devolución de llamada, consulte la respuesta de Felix.

Ahora, definamos foo para actuar en consecuencia

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

(violín)

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

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

1035
whackamadoodle3000 1 mar. 2018 a las 01:03