ฉันจะส่งคืนการตอบสนองจากการเรียก Observable / http / async ในเชิงมุมได้อย่างไร


110

ฉันมีบริการที่ส่งคืนค่าที่สังเกตได้ซึ่งส่งคำขอ http ไปยังเซิร์ฟเวอร์ของฉันและรับข้อมูล ฉันต้องการที่จะใช้ข้อมูลนี้ undefinedแต่ฉันมักจะจบลงด้วยการ มีปัญหาอะไร?

บริการ :

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

ส่วนประกอบ:

@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!
  }
}

ฉันตรวจสอบฉันจะตอบกลับการตอบกลับจากการโทรแบบอะซิงโครนัสได้อย่างไร โพสต์ แต่ไม่พบวิธีแก้ปัญหา


2
นั่นจะเป็นจุดที่ดีในการเน้นย้ำถึงความจริงที่ว่าไม่สามารถเปลี่ยนการดำเนินการแบบอะซิงโครนัสเป็นซิงโครนัส - วันได้
n00dl3

@ n00dl3 ขอบคุณสำหรับคำแนะนำ! ฉันได้พยายามอธิบายในส่วน "สิ่งที่คุณไม่ควรทำ:" อย่าลังเลที่จะแก้ไข
eko


@HereticMonkey โพสต์นั้นได้รับเครดิตแล้วในคำตอบ
eko

@eko และ? นั่นทำให้คำถามซ้ำกันน้อยลงหรือไม่?
ลิงนอกรีต

คำตอบ:


117

เหตุผล:

เหตุผลก็undefinedคือคุณกำลังทำการดำเนินการแบบอะซิงโครนัส หมายความว่าจะใช้เวลาสักครู่ในการดำเนินการตามgetEventListวิธีนี้ (ส่วนใหญ่ขึ้นอยู่กับความเร็วเครือข่ายของคุณ)

ลองดูที่การโทร http

this.es.getEventList()

หลังจากที่คุณสร้าง ("ไฟ") คำขอ http กับsubscribeคุณแล้วคุณจะต้องรอการตอบกลับ ในขณะที่รอจาวาสคริปต์จะดำเนินการบรรทัดด้านล่างโค้ดนี้และหากพบการกำหนด / การดำเนินการแบบซิงโครนัสมันจะดำเนินการทันที

ดังนั้นหลังจากสมัครรับข้อมูลgetEventList()และรอการตอบกลับ

console.log(this.myEvents);

บรรทัดจะถูกดำเนินการทันที และมูลค่าของมันคือundefinedก่อนที่การตอบสนองจะมาถึงจากเซิร์ฟเวอร์ (หรืออะไรก็ตามที่คุณได้กำหนดค่าเริ่มต้นตั้งแต่แรก)

คล้ายกับการทำ:

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

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


สารละลาย:

แล้วเราจะเอาชนะปัญหานี้ได้อย่างไร? เราจะใช้ฟังก์ชันเรียกกลับซึ่งเป็นsubscribeวิธีการ เนื่องจากเมื่อข้อมูลมาถึงจากเซิร์ฟเวอร์ข้อมูลจะอยู่ภายในsubscribeพร้อมกับการตอบสนอง

ดังนั้นการเปลี่ยนรหัสเป็น:

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

จะพิมพ์ตอบกลับ .. หลังจากนั้นสักครู่


สิ่งที่คุณควรทำ:

อาจมีหลายสิ่งที่ต้องทำกับคำตอบของคุณนอกเหนือจากการบันทึก คุณควรดำเนินการทั้งหมดเหล่านี้ภายในการเรียกกลับ (ภายในsubscribeฟังก์ชัน) เมื่อข้อมูลมาถึง

สิ่งที่ต้องพูดถึงก็คือถ้าคุณมาจากPromiseพื้นหลังการthenเรียกกลับจะสอดคล้องsubscribeกับสิ่งที่สังเกตได้


สิ่งที่คุณไม่ควรทำ:

คุณไม่ควรพยายามเปลี่ยนการดำเนินการ async เป็นการดำเนินการซิงค์ (ไม่ใช่ว่าทำได้) เหตุผลประการหนึ่งที่เรามีการดำเนินการ async คือการไม่ทำให้ผู้ใช้รอให้การดำเนินการเสร็จสิ้นในขณะที่พวกเขาสามารถทำสิ่งอื่น ๆ ได้ในช่วงเวลานั้น สมมติว่าหนึ่งในการดำเนินการ async ของคุณใช้เวลา 3 นาทีในการดำเนินการให้เสร็จสมบูรณ์หากเราไม่มีการดำเนินการ async อินเทอร์เฟซจะค้างเป็นเวลา 3 นาที


การอ่านที่แนะนำ:

เครดิตเดิมสำหรับคำตอบนี้ไปที่: ฉันจะส่งคืนการตอบกลับจากการโทรแบบอะซิงโครนัสได้อย่างไร

แต่ด้วยรุ่น angular2 เราได้รับการแนะนำให้รู้จักกับ typescript และ observables ดังนั้นคำตอบนี้หวังว่าจะครอบคลุมพื้นฐานของการจัดการคำขอแบบอะซิงโครนัสกับสิ่งที่สังเกตได้


3
เมื่อผู้ถามตอบคำถามในเวลาเดียวกันของโพสต์ .. นี่เป็นจุดที่ดีที่จะหยุดและเขียนบล็อกโพสต์แทน
Jonas Praem

3
@JonasPraem จริง แต่ไม่มีอะไรผิดในการแบ่งปันความรู้บน Stackoverflow เมื่อคุณเห็นจำนวนการโหวตมันช่วยคนจำนวนมากที่นี่และจะทำเช่นนั้นต่อไป
Amit Chigadani

11

การโทร http ในเชิงมุม / จาวาสคริปต์เป็นการดำเนินการแบบอะซิงโครนัส ดังนั้นเมื่อคุณเรียกใช้ http มันจะกำหนดเธรดใหม่เพื่อสิ้นสุดการเรียกนี้และเริ่มดำเนินการบรรทัดถัดไปด้วยเธรดอื่น นั่นคือเหตุผลที่คุณได้รับคุณค่าที่ไม่ได้กำหนด ดังนั้นให้ทำการเปลี่ยนแปลงด้านล่างเพื่อแก้ไขปัญหานี้

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

5

คุณสามารถใช้asyncPipe ได้หากคุณใช้ myEvents ในเทมเพลตเท่านั้น

ตัวอย่างที่มี asyncPipe และ Angular4 HttpClient ตัวอย่าง


1
และ (!) ด้วยการใช้งานนี้เราไม่จำเป็นต้องยกเลิกการสมัครสมาชิก: medium.com/@sub.metu/…
San Jay Falcon

@SanJayFalcon เนื่องจากนี่เป็นคำขอ http จึงไม่จำเป็นต้องยกเลิกการสมัคร
AJT82

3

นักสังเกตการณ์ขี้เกียจดังนั้นคุณต้องสมัครสมาชิกเพื่อรับค่า คุณสมัครรับข้อมูลอย่างถูกต้องในรหัสของคุณ แต่ได้บันทึกผลลัพธ์ไว้นอกบล็อก 'สมัครสมาชิก' พร้อมกัน นั่นเป็นเหตุผลว่าทำไมจึง "ไม่ได้กำหนด"

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

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

ดังนั้นหากคุณบันทึกไว้ในบล็อกสมัครสมาชิกระบบจะบันทึกการตอบสนองอย่างถูกต้อง

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

3

นี่คือปัญหาที่คุณจะเริ่มต้นthis.myEventsเข้ามาsubscribe()ซึ่งเป็นบล็อกไม่ตรงกันในขณะที่คุณกำลังทำconsole.log()เพียงแค่ออกจากsubscribe()บล็อก ดังนั้นconsole.log()การเรียกก่อนที่this.myEventsจะเริ่มต้น

กรุณาย้ายรหัส console.log () ของคุณภายใน subscribe () และคุณทำเสร็จแล้ว

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

2

ผลลัพธ์ไม่ได้กำหนดเนื่องจากกระบวนการเชิงมุมไม่ตรงกัน คุณสามารถลองได้ดังนี้:

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

1

ตรวจสอบให้แน่ใจว่าคุณได้จับคู่การตอบสนองของคุณกับเอาต์พุต json มิฉะนั้นจะส่งกลับข้อความธรรมดา คุณทำเช่นนี้:

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

ไม่ได้กำหนดเนื่องจากค่าที่นี่ถูกบันทึกก่อนที่ข้อมูลใด ๆ จากบริการจะถูกตั้งค่าจากการเรียกใช้บริการข้างต้น ดังนั้นคุณต้องรอจนกว่าการโทรของ ajax จะเสร็จสิ้นและตั้งค่าข้อมูลจากข้อมูลการตอบกลับ

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

ที่นี่ให้บันทึกคอนโซลภายในวิธีสมัครสมาชิกที่จะสร้างบันทึกเมื่อข้อมูลถูกตั้งค่าในตัวแปร myEvents

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

0

คุณสามารถลองใช้วิธีนี้ -

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');
    }
โดยการใช้ไซต์ของเรา หมายความว่าคุณได้อ่านและทำความเข้าใจนโยบายคุกกี้และนโยบายความเป็นส่วนตัวของเราแล้ว
Licensed under cc by-sa 3.0 with attribution required.