fetch en clase me devuelve undefined | javascript

Iniciado por Drakaris, 15 Mayo 2021, 16:10 PM

0 Miembros y 1 Visitante están viendo este tema.

Drakaris

En el HTML llamo al script panel.js con tipo module
Código (html) [Seleccionar]
<script src="../js/panel.js" type="module"></script>

Ahroa bien en el archivo panel.js
panel.js
Código (javascript) [Seleccionar]

import {DB} from './crud.js';
const API='http://localhost:5000/miramar';
const formLogin=document.querySelector("body #formLogin");

const login=(e)=>{
   e.preventDefault();
   let password = formLogin['pass'].value;
   let user = formLogin['user'].value;
   let empleados=new DB(`${API}/empleados/`).show();
   console.log(empleados);
}

const init=()=>{
   formLogin.addEventListener("submit",login);
}

init();

Cuando hago click en submit del formulario este me recoge los datos del formulario y también quiero que me recoja los datos de una peticion fetch a una API que tengo.
Para ello creo la clase DB() con el metodo show() para que me devuelva el objecto.

La clase esta definida en el archivo crud.js
Código (javascript) [Seleccionar]

export class DB{
   constructor(API){
       this.API=API;
       this.result;
   }
   show(){
       fetch(this.API)
       .then(res=>res.json())
       .then(data=>this.result=data);
       return this.result;
   }
}


el problema que tengo es que en el archivo panel.js hago
Código (javascript) [Seleccionar]

   let empleados=new DB(`${API}/empleados/`).show();
   console.log(empleados);


y me devuelve undefined.

Ahora bien.
Si lo hago con promesas, me devuelve que el estado de la promesa es pending

Código (javascript) [Seleccionar]

export class DB{
   constructor(API){
       this.API=API;
       this.result;
   }
   async show(){
       /*fetch(this.API)
       .then(res=>res.json())
       .then(data=>this.result=data);
       return this.result;*/
       let res = await fetch(this.API);
       let data = await res.json();
       this.result=data;
       return this.result;
   }


Pero si en el console.log le pongo un setTImeout de 1 segundo me devulve la promesa con los valores, pero no puedo obtener sus valores, o como los obtengo?
Código (javascript) [Seleccionar]

  setTImeout(()=>console.log(empleados),1000)

Pero si el setTimeout lo hago dentro de la clase no me funciona, solo en el panel.js
PD.
Los datos que quiero coger son:
Código (json) [Seleccionar]

[
 {
   "APELLIDOS": [
     "León",
     "Guerrero"
   ],
   "_id": "609e833e6746526127be53e6",
   "NOMBRE": "Jhilmar",
   "NACIMIENTO": "2002-02-19T00:00:00.000Z",
   "GENERO": "M",
   "CONTRASENA": "Jhilmar"
 },
 {
   "APELLIDOS": [
     "Antonio",
     "Salazar"
   ],
   "_id": "609e83f26746526127be53e7",
   "NOMBRE": "Juan",
   "NACIMIENTO": "2002-02-19T00:00:00.000Z",
   "GENERO": "M",
   "CONTRASENA": "Juan Antonio"
 },
 {
   "APELLIDOS": [
     "Napola"
   ],
   "_id": "609e84116746526127be53e8",
   "NOMBRE": "Carmen",
   "NACIMIENTO": "2002-02-19T00:00:00.000Z",
   "GENERO": "F",
   "CONTRASENA": "Carmen"
 }

https://drive.google.com/drive/folders/1pSGu0ql9ZklDvDCu56vuh16FYB-HPwbU?usp=sharing

Gracias
Lo increible, no es lo que ves, sino como es

MinusFour

El típico problema de javascript en la que las personas intentan escapar el flujo del programa.

E.g.

Código (javascript) [Seleccionar]
let result;
setTimeout(function(){ result = 'algo diferente'; }, 0);
console.log(result); //undefined


Pero lo haz puesto en una clase...
Código (javascript) [Seleccionar]
class A {
   metodo(){
       setTimeout(_ => this.result = 'algo diferente');
       return this.result;
   }
}


Esto simplemente está mal. En el ejemplo, estoy usando setTimeout pero en javascript hay cientos de funciones que son asincronas, sin mencionar las funciones que son compuestas por funciones asincronas y que por ende también son asincronas. En tu caso, tu has usado fetch, pero es el mismo problema.

Una función asíncrona no está obligada a llamar a la función pasada inmediatamente (de hecho, esto casi nunca pasa así). Estas son funciones que agendan la ejecución de una función posteriormente. Es un error creer que la función ha sido llamada antes de la instrucción que asigna el valor (o retorna el valor en tu caso).

Es como si pidieras una pizza y le dejas la instrucción al repartidor que deje la pizza en la puerta. No tratarías de recoger la pizza en la puerta justo después de colgarle a la pizzeria ¿verdad? Pues es lo mismo con javascript. Tu pizza no está hecha, pero la estas intentando poner en la mesa. ¿Pues que has agarrado? Nada, undefined.

Lo correcto es seguir el flujo de ejecución. Si pediste una pizza, tienes que recoger la pizza cuando llegue a tu puerta. Para esto están los mecanismos de promesas, async/await (basados en promesas) y CPS (que está en desuso).

No puedes escapar el flujo de estos mecanismos. Una vez que una función use una función asincrona, tu función pasa también a ser asíncrona.

Esto quiere decir que lo podrías hacer así:

Código (javascript) [Seleccionar]
export class DB{
    constructor(API){
        this.API=API;
    }
    show(){
        return fetch(this.API)
        .then(res=>res.json())
    }
}


O así:
Código (javascript) [Seleccionar]

    constructor(API){
        this.API=API;
    }
    async show(){
        let res = await fetch(this.API);
        return res.json();
    }


Para luego consumirlo así:

Código (javascript) [Seleccionar]
const login=(e)=>{
    //...
    let empleados=new DB(`${API}/empleados/`).show();
    empleados.then(data => console.log(data);
   //...
}


Vaya que solo puedes continuar el flujo dentro de la función pasada a then, pues esta agenda la función para que sea ejecutada una vez que el valor exista. En pocas palabras, el metodo then te permite recoger la pizza cuando este en tu puerta.

Drakaris

#2
ok, muchas gracias. cuando dices
"Una función asíncrona no está obligada a llamar a la función pasada inmediatamente[...]", te  refieres que si tengo
Código (javascript) [Seleccionar]

   function show(){
      let result;
       fetch(this.API)
       .then(res=>res.json())
       .then(data=>result=data);
       return result;
   }


la funcion con el parametro 'data' no puede guardar el contenido del 'data' en la variable 'result' (que esta definida fuera del fetch()) y esta retornarla? Es decir, el contenido de 'data' esta siempre 'enjaulado' dentro del .then, no puede salir de el. Es eso?
Lo increible, no es lo que ves, sino como es

MinusFour

Si puedes usar la variable result, lo que no puedes hacer es usarla en ese momento. De hecho, tu podrías revisar el valor de esa variable desde tu consola y verías los resultados correctos. Sin embargo, al imprimir la variable en esa parte del script, seguirás viendo undefined.

Código (javascript) [Seleccionar]

let result;
setTimeout(_=>result=1, 10000);
console.log(result); //undefined;


Por partes:

Código (javascript) [Seleccionar]
let result;

Declaras una variable result, nada fuera de lo extraordinario.

Código (javascript) [Seleccionar]
setTimeout(..., 10000);

Corre una función en 10,000 milisegundos, osea 10 segundos.

Código (javascript) [Seleccionar]
_ => result=1

Función que asigna en result el valor de 1. Está función es llamada 10 segundos después de que se haya agendado.

Código (javascript) [Seleccionar]
console.log(result);

Imprime el valor de la variable result.

¿Cual crees que sea el resultado de result si la función que asigna el valor a result no se ejecuta hasta después de 10 segundos? Pues undefined.

Al final tienes:

1. Declaras variable
2. Agendas una función a ejecutarse 10,000 ms después.
3. Imprimes el valor de la variable.
4. Asignas un valor a la variable 10,000 ms después.

¿Ahora, quizás estés pensando, bueno pero y si la función la hubiese agendado en 0 milisegundos? ¿Se ejecutará inmediatamente ahí?

Y la respuesta es NO.

Esto es por el mecanismo interno con el cual trabajan los entornos de javascript, el ciclo de eventos (Event Loop).  Los entornos de javascript corren un determinado código de javascript de inicio a final y una vez terminado la ejecución del código, entran en un ciclo hasta que detecten nuevo código para ejecutar. En este caso la función setTimeout agenda una función con el entorno de javascript y una vez que nuestro código termine (al imprimir el variable), el entorno de javascript entra en un ciclo y empieza a preguntar si hay algún nuevo evento disponible. Una vez que hayan pasado 10,000 milisegundos el evento se dispara y el entorno es notificado que una función debe correrse.

En pocas palabras.. puedes considerar que las funciones asíncronas ejecutan la función después que el script haya terminado (pero que tan después dependerá de la función asincrona).

Esto es lo mismo con fetch. Quizás piensas que la respuesta del servidor está en cache y que puede responder inmediatamente pero esto no es así.

El patrón en el cual se intenta guardar en una variable dentro del léxico superior en una llamada a una función asíncrona es por lo general erroneo.