0

Estoy haciendo una petición post a una api, lo que quiero es poder guardar la respuesta a fuera de la función, entonces como puedo hacer para que funcione el console.log(a) fuera de la función.

xhr.addEventListener("readystatechange", function() {
    if(this.readyState === 4) {
        a = this.responseText; //retorna un entero
    }
});

console.log(a);

También puedo hacer la petición con fetch, en el cual tengo el mismo problema.

var requestOptions = {
  method: 'POST',
  headers: myHeaders,
  body: raw,
  redirect: 'follow'
};

fetch(url, requestOptions)
  .then(response => response.text())
  .then(result => console.log(result))
  .catch(error => console.log('error', error));
Rodrigo Ramírez
  • 5,822
  • 4
  • 11
  • 32
Javier
  • 304
  • 2
  • 14
  • declarandola fuera, asignando valor dentro y volviendo a llamar desde afuera... – Bryro Feb 04 '21 at 14:15
  • al hacer eso me imprime la que declaro fuera @Bryro – Javier Feb 04 '21 at 14:23
  • 1
    Tal vez te interese leer [esta pregunta](https://es.stackoverflow.com/questions/1539/c%c3%b3mo-obtener-la-respuesta-de-una-llamada-as%c3%adncrona-ajax-fuera-de-ella) – Triby Feb 04 '21 at 15:45
  • Además de lo que comenta Triby te preguntaría por qué quieres tener esa variable de manera global. Esa práctica es peligrosa. – Kiko_L Feb 04 '21 at 16:11
  • Pretendía tener esa variable de forma global, para poder agregarla a un objeto Json, pero me di cuenta que no era necesario, ya que podía declarar el objeto Json fuera de la función, y la variable 'a' la puedo agregar desde la función (sin necesidad de hacerla global) @Kiko_L – Javier Feb 04 '21 at 16:59
  • 1
    @Triby creo que es mejor marcarla como duplicado – Juan Rivera Feb 04 '21 at 17:24
  • @JuanRivera elimino la pregunta? – Javier Feb 04 '21 at 21:15
  • @Javier no, solo acepta para marcarla como duplicado, asi cuando otros usuarios pregunten algo parecido a lo tuyo, ya tendran una respuesta en la otra pregunta – Juan Rivera Feb 04 '21 at 21:29

1 Answers1

0

Lo que pasa es que no estas teniendo en cuenta 3 cosas:

  • El uso de var se considera deprecado, sin embargo el uso de var exactamente en estos momentos serviria para lo que necesitas si no fuera por los siguientes dos puntos.

  • Los eventos oyentes (listeners) son enrealidad funciones con delay, que se disparan dependiendo una condicion o condiciones especiales, esto es lo mas parecido a manejar código asincrono, ya que aunque los listeners no hagan uso de promises, si intentas asignar una variable desde dentro del cuerpo de un listener y luego la imprimes afuera puede dar la ilusion de que la variable no cambia, debido a que no estas teniendo en cuenta el asincronismo, te doy un ejemplo, tomemos lo que tu tienes de codigo:

    //Se ejecuta tiempo despues y deacuerdo a una condicion
    xhr.addEventListener("readystatechange", function() {
       if (this.readyState === 4) {
           a = this.responseText; //retorna un entero
       }
    });
    
    //Se ejecuta al instante
    console.log(a);
    

Este es justo el problema.

  • En primeras no deberias tratar de guardar valores asincronos en una variable global, puesto que no es posible de manera directa, ya que aunque los eventos oyentes no utilicen promises los valores almacenados en la variable pueden no estar sincronizados con los actuales, por este motivo cuando tu intentes rescatar el ultimo valor de un evento oyente puede pasar de que el valor "nunca cambie", y digo entre comillas, porque enrealidad el valor puede o no estar cambiando, lo que pasa es que en este caso el proceso es asincrono (esta cambiando en el tiempo), mientras que probablemente el codigo que intente rescatar el valor de esa variable no esta esperando a la terminacion del codigo asincrono.

Conclusión?, lo mejor en este caso seria simplemente usar promises para manejar el codigo asincrono, ya que en este caso se trata de una peticion ajax.

Recordemos que AJAX traduce: Asynchronous Javascript And XML por lo que estamos usando codigo asincrono.

Por tanto al estar usando codigo asincrono en un flujo sincrono (Javascript es sincrono por defecto), lo mejor seria no preocuparnos por guardar esta informacion en alguna variable de manera directa (tendrias problemas con el flujo asincrono).

En ves de ello lo que podríamos hacer es un proceso llamado promisificacion, el cual consiste en utilizar una utilidad de ES6 llamada Promise que nos permite envolver el codigo asincrono y manejarlo de forma mas sencilla.

Vamos a promisificar tu ejemplo:

function getData(){
  return new Promise((res, rej) => {
    //Aca arriba creas la peticion XMLHttpRequest
  
    //Añades el evento oyente y esperas a que la peticion cargue
    xhr.addEventListener("readystatechange", () => {
    
      //Cuando la peticion este lista resolvemos la promesa usando el callback res
      if(this.readyState === 4) res(xhr.responseText);
    });
    
    //En caso de error usamos el callback rej
    xhr.onerror = e => rej(e.message);
    
  });
}

getData().then(data =>{
  console.log(data);    //Data disponible SOLAMENTE AQUI DENTRO
}).catch(e => {
  console.error(e);     //Se mostrara el mensaje de error en caso de haberlo
});

Nota: recuerda, como se trata de codigo asincrono no vas a poder directamente asignar un valor a una variable por fuera dentro de .then o .catch, ya que esta variable quedara con un valor indefinido, independientemente de si ha entrado al then o al catch, simplemente no trates de guardar el contenido de la peticion en una variable.

En caso de que necesites usar el valor traido por ajax en mas de una parte te recomendaria que mas bien guardases la ejecucion de tu promise en vez de intentar extraer el valor directamente desde la promise, ya que solo tendras dolores de cabeza si lo intentas.

Por ejemplo, si requirieras la informacion en varias partes podrias hacer algo como esto:

function getData(){
  return new Promise((res, rej) => {
    //Aca arriba creas la peticion XMLHttpRequest
  
    //Añades el evento oyente y esperas a que la peticion cargue
    xhr.addEventListener("readystatechange", () => {
    
      //Cuando la peticion este lista resolvemos la promesa usando el callback res
      if(this.readyState === 4) res(xhr.responseText);
    });
    
    //En caso de error usamos el callback rej
    xhr.onerror = e => rej(e.message);
    
  });
}

const promiseData = getData();    //Almacena una promise ya ejecutada

//OPCION 1: UTILIZAR UNA FUNCION ASINCRONA Y AWAIT
async function tratarData(){

  //Await espera a que se resuelva la promise para almacenar el valor directamente
  //solo puede ser usado en un contexto asincrono
  const data = await promiseData;
  console.log(data);
  
}

//OPCION 2: UTILIZAR UNA FUNCION SINCRONA
function tratarData2(){

  promiseData.then(data =>{
    console.log(data);
  }).catch(e => {
    console.error(e);
  });

}

//POSIBILIDAD
//En el scope GLOBAL no se puede usar await, por ende no trates de usar await
//En vez de ello si necesitas el valor en el scope global deberas usar .then y .catch:
promiseData.then(data => {

  //Notese que el valor data no podra ser asignado a algo externo, si lo haces
  //La variable quedara como undefined al tratarse de codigo asincrono, asi que
  //Todos tus procedimientos deberas hacerlos aqui dentro.
  console.log(data);
}).catch(e => {
  console.error(e);
});
Riven
  • 5,728
  • 2
  • 7
  • 27