¿Cómo devuelvo la respuesta de una llamada Observable / http / async en angular?


110

Tengo un servicio que devuelve un observable que hace una solicitud http a mi servidor y obtiene los datos. Quiero usar estos datos pero siempre termino obteniendo undefined. ¿Cuál es el problema?

Servicio :

@Injectable()
export class EventService {

  constructor(private http: Http) { }

  getEventList(): Observable<any>{
    let headers = new Headers({ 'Content-Type': 'application/json' });
    let options = new RequestOptions({ headers: headers });

    return this.http.get("http://localhost:9999/events/get", options)
                .map((res)=> res.json())
                .catch((err)=> err)
  }
}

Componente:

@Component({...})
export class EventComponent {

  myEvents: any;

  constructor( private es: EventService ) { }

  ngOnInit(){
    this.es.getEventList()
        .subscribe((response)=>{
            this.myEvents = response;
        });

    console.log(this.myEvents); //This prints undefined!
  }
}

Revisé ¿Cómo devuelvo la respuesta de una llamada asincrónica? publicar pero no pude encontrar una solución


2
ese sería un buen punto para enfatizar el hecho de que no es posible transformar una operación asincrónica en una sincrónica.
n00dl3

@ n00dl3 ¡Gracias por el consejo! Intenté explicarlo en la sección "Qué no debes hacer". Siéntete libre de editarlo.
eko


@HereticMonkey esa publicación ya está acreditada en la respuesta
eko

@eko ¿Y? ¿Eso hace que la pregunta sea ​​menos duplicada?
Heretic Monkey

Respuestas:


117

Razón:

El motivo undefinedes que está realizando una operación asincrónica. Lo que significa que tomará algún tiempo completar elgetEventList método (dependiendo principalmente de la velocidad de su red).

Así que veamos la llamada http.

this.es.getEventList()

Después de hacer ("disparar") su solicitud http subscribe, estará esperando la respuesta. Mientras espera, javascript ejecutará las líneas debajo de este código y si encuentra asignaciones / operaciones sincrónicas, las ejecutará inmediatamente.

Entonces, después de suscribirse al getEventList()y esperar la respuesta,

console.log(this.myEvents);

La línea se ejecutará inmediatamente. Y su valor es undefinedantes de que llegue la respuesta del servidor (o lo que sea que lo haya inicializado en primer lugar).

Es similar a hacer:

ngOnInit(){
    setTimeout(()=>{
        this.myEvents = response;
    }, 5000);

    console.log(this.myEvents); //This prints undefined!
}


Solución:

Entonces, ¿cómo superamos este problema? Usaremos la función de devolución de llamada que es el subscribemétodo. Porque cuando los datos lleguen del servidor estarán dentro del subscribecon la respuesta.

Entonces cambiando el código a:

this.es.getEventList()
    .subscribe((response)=>{
        this.myEvents = response;
        console.log(this.myEvents); //<-- not undefined anymore
    });

imprimirá la respuesta .. después de algún tiempo.


Qué debes hacer:

Puede que haya muchas cosas que hacer con su respuesta además de registrarla; debe hacer todas estas operaciones dentro de la devolución de llamada (dentro de la subscribefunción), cuando llegan los datos.

Otra cosa a mencionar es que si viene de un Promisefondo, la thendevolución de llamada se corresponde subscribecon los observables.


Qué no debes hacer:

No debe intentar cambiar una operación asíncrona a una operación de sincronización (no es que pueda). Una de las razones por las que tenemos operaciones asíncronas es para no hacer que el usuario espere a que se complete una operación mientras puede hacer otras cosas en ese período de tiempo. Suponga que una de sus operaciones asíncronas tarda 3 minutos en completarse, si no tuviéramos las operaciones asíncronas, la interfaz se congelaría durante 3 minutos.


Lectura sugerida:

El crédito original de esta respuesta es: ¿Cómo devuelvo la respuesta de una llamada asincrónica?

Pero con la versión angular2 nos presentaron a mecanografiado y observables, por lo que es de esperar que esta respuesta cubra los conceptos básicos del manejo de una solicitud asincrónica con observables.


3
Cuando el interrogador responde una pregunta en el mismo momento exacto de la publicación ... Este es un buen lugar para detenerse y escribir una publicación de blog en su lugar
Jonas Praem

3
@JonasPraem Es cierto, pero no hay nada de malo en compartir el conocimiento en Stackoverflow. Como puede ver el número de votos, ayudó a mucha gente aquí y seguirá haciéndolo.
Amit Chigadani

11

Hacer una llamada http en angular / javascript es una operación asincrónica. Entonces, cuando realice una llamada http, asignará un nuevo hilo para finalizar esta llamada e iniciar la ejecución de la siguiente línea con otro hilo. Es por eso que obtiene un valor indefinido. así que haz el cambio a continuación para resolver esto

this.es.getEventList()  
      .subscribe((response)=>{  
       this.myEvents = response;  
        console.log(this.myEvents); //<-this become synchronous now  
    });


3

Los observables son perezosos, por lo que debe suscribirse para obtener el valor. Lo suscribió correctamente en su código, pero simultáneamente registró la salida fuera del bloque 'suscribirse'. Por eso es 'indefinido'.

ngOnInit() {
  this.es.getEventList()
    .subscribe((response) => {
        this.myEvents = response;
    });

  console.log(this.myEvents); //Outside the subscribe block 'Undefined'
}

Entonces, si lo registra dentro del bloque de suscripción, registrará la respuesta correctamente.

ngOnInit(){
  this.es.getEventList()
    .subscribe((response)=>{
        this.myEvents = response;
        console.log(this.myEvents); //Inside the subscribe block 'http response'
    });
}

3

Aquí el problema es que se está inicializando this.myEventsen subscribe()un bloque asincrónico, mientras que está console.log()saliendo del subscribe()bloque. Así console.log()que this.myEventsse inicializa la llamada antes .

Mueva su código console.log () también dentro de subscribe () y listo.

ngOnInit(){
    this.es.getEventList()
        .subscribe((response)=>{
            this.myEvents = response;
            console.log(this.myEvents);
        });
  }

2

El resultado no está definido porque el proceso angular es asíncrono. puedes intentar lo siguiente:

async ngOnInit(){
    const res = await this.es.getEventList();
    console.log(JSON.stringify(res));
}

1

También asegúrese de asignar su respuesta a una salida json. De lo contrario, devolverá texto sin formato. Hazlo así:

getEventList(): Observable<any> {
let headers = new Headers({ 'Content-Type': 'application/json' });
let options = new RequestOptions({ headers: headers });

return this.http.get("http://localhost:9999/events/get", options)
            .map((res)=>{ return res.json();}) <!-- add call to json here
            .catch((err)=>{return err;})
}

1

Indefinido porque el valor aquí se registra antes de que se establezca cualquier dato del servicio a partir de la llamada de servicio de suscripción anterior. Por lo tanto, debe esperar hasta que finalice la llamada ajax y configurar los datos de los datos de respuesta.

getEventList(): Observable<any>{
    let headers = new Headers({ 'Content-Type': 'application/json' });
    let options = new RequestOptions({ headers: headers });

    return this.http.get("http://localhost:9999/events/get", options)
                .map((res)=> res.json())
                .catch((err)=> err)
  }

Aquí haga el registro de la consola dentro del método de suscripción que hará el registro cuando los datos se establezcan en la variable myEvents.

ngOnInit(){
    this.es.getEventList()
        .subscribe((response)=>{
            this.myEvents = response;
     // This prints the value from the response
    console.log(this.myEvents)
        }); 
  }

0

Simplemente puede probar este método:

let headers = new Headers({'Accept': 'application/json'});
let options = new RequestOptions({headers: headers});

return this.http
    .get(this.yourSearchUrlHere, options) // the URL which you have defined
    .map((res) => {
        res.json(); // using return res.json() will throw error
    }
    .catch(err) => {
        console.error('error');
    }
Al usar nuestro sitio, usted reconoce que ha leído y comprende nuestra Política de Cookies y Política de Privacidad.
Licensed under cc by-sa 3.0 with attribution required.