2016-07-29 17 views
0

我在一個應用程序中顯示有關'Modyules'(在課程中)必須通過兩個http.gets(我不能控制apis不幸)的信息:通過在ng2中的可觀察對象的依賴和並行請求

  1. 獲取Modyule.ids列表 - 需要存儲的ID
  2. 對於每個Modyule.id另一個http.get得到Modyule.name

在我modyule.component.ts ,我有:

this.modyuleService.getModyules() 
    .subscribe(
      modyules => this.modyules = modyules, 
      error => this.errorMessage = <any>error 
    ); 

在我modyule.service.ts,我有:

getModyules(): Observable<Modyule[]> { 
     return this.http.get(listOfModyuleIdsUrl + '.json') 
      .map(this.getModyulesDetails) 
      .catch(this.handleError); 
    } 

但getModyuleDetails是我很努力。這是我有這麼遠,主要是基於:

http://www.syntaxsuccess.com/viewarticle/angular-2.0-and-http

,看着,但沒有看到我如何申請:

https://stackoverflow.com/a/35676917/2235210

private getModyulesDetails = (res: Response) => { // instance method for defining function so that the this.http below refers to the class. 
    let listOfModyules = res.json(); 
    let modyulesToReturn = []; 

    for (let individualModyule of listOfModyules){ 
     let tempModyule = new Modyule; 
     tempModyule.id = individualModyule.id; 

     this.http.get(individualModyuleUrl + individualModyule.id + '.json') 
      .map((res: Response) => res.json())) 
      .subscribe(res => { 
       tempModyule.name = res.name 
       modyulesToReturn.push(tempModyule); 
      }); 
     } 
    return modyulesToReturn;  
} 

現在,這是在我的本地機器上工作,在那裏我將.json響應作爲靜態.json文件來模擬。但是,當我處理真正的api時,我無法依賴for循環中的http.gets完成在返回modyulesToReturn之前。

我已經在forkJoin上做了幾次嘗試,但沒有看到如何將它與需要從第一個http.get捕獲Modyule.id的要求結合起來。

我非常感謝任何有關如何執行並行請求的指針,這些請求依賴於初始請求,但能夠將來自兩者的數據組合起來。

編輯在向效應初探討論,@馬特和@batesiiic:

因此,在@ batesiiic的建議getModyulesDetails的重寫拿起返回一個主題,我不能讓我的頭周圍的問題是如何填充在Modyule.id在第一個呼叫,然後Modyule.name在第二次調用(不返回任何Modyule.id參考),即:

private getModyulesDetails = (res: Response) => { // instance method for defining function so that the this.http below refers to the class. 
    let listOfModyules = res.json(); 
    let modyulesToReturn = []; 
    let calls = []; 

    for (let individualModyule of listOfModyules){ 
     /* I REALLY NEED TO POPULATE Modyule.id HERE */ 
     calls.push(
      this.http.get(individualModyuleUrl + individualModyule.id + '.json') 
       .map((res: Response) => res.json())) 
     ); 
    } 

    var subject = new Subject<Modyules[]>(); 

    Observable.zip.apply(null, calls).subscribe((modyules: Modyules[]) => { 
     var modyulesToReturn = []; 
     modyules.forEach(modyule => { 
      let tempModyule = new Modyule; 
      /*I COULD POPULATE Modyle.name HERE BUT IT WON'T MATCH THE Modyule.id SET ABOVE - EXCEPT BY LUCK*/ 
      modyulesToReturn.push(tempModyule); 
     } 
     subject.next(modyulesToReturn); 
    }); 

    return subject;  
} 

我想我需要打的電話一個通過for循環中的一個,但以某種方式等待,直到他們已經迴應modyulesToReturn之前所有響應?

回答

1

如果一個請求依賴於另一個請求返回的數據(對於您的情況,您需要module.id,然後才能撥打module.name或其詳細信息),則應在第一個請求的回調中啓動依賴請求。

this.modyuleService.getModyules() 
.subscribe(
     modyules => { 
     // start your request(s) for the details here 
     }, 
     error => ... 
); 

Furthemore,您使用下面的結構:

getModyulesDetails() { 
    // this is what you want to return 
    let modyulesToReturn = []; 

    for (let individualModyule of listOfModyules){ 
    //.. you are starting asynchronous tasks here, 
    // and fill your modyulesToReturn in their callbacks 
    } 

    // instantly return the variable that is filled asynchrously 
    return modyulesToReturn; 
}  

這可不行,你打算的方式。 for循環中的http請求是異步的,即他們開始了,但是當前的線程不會等到他們完成,而是繼續下一個迭代for loop。當你從你的方法返回時,你的訂閱中的回調很可能還沒有被調用。當他們這樣做時,你已經返回了你的(可能)空數組modyulesToReturn

因此,您不得同步返回。相反,您應該提供一種返回Observable的方法,當您收到另一個modyule詳細信息時,該方法每次都會觸發。需要這些詳細信息的組件將自行訂閱該Observable並同時接收更新。

如果您開始使用異步任務,則需要將該「設計」推送到您的組件。

+0

感謝@馬特。我可能會誤解,但這不是我通過在地圖回調(在服務中)中調用getModyulesDetails中的細節來收集有效信息嗎?我仍然不確定如何讓getModyule等到getModyuleDetails(即使插入的地方你提出作爲一個服務方法,它自己的訂閱回調)已經返回...做一個.subscribe回調,其中包含另一個.subscribe回調有等到第二次訂閱被調用? – theotherdy

+0

謝謝@Matt。你的解釋非常清楚。 batesiiic +例如http://stackoverflow.com/a/38156976/2235210建議使用Rx.Subjects,但我真的不明白這是如何工作的。我是否正確地閱讀你所說的,因爲我應該避免單個組件中的這些依賴/鏈接請求,而是創建例如觀察Observable getModyulesDetails方法的ModyuleDetailComponent?在這種情況下,我該如何讓ModyuleDetailComponent知道ModyuleComponent已經完成獲取Modyule.ids的列表,以便我可以例如@Input()他們到ModyuleDetailComponent? – theotherdy

+1

這取決於。 batesiiic的方法是將所有東西都捆綁在一起,這樣Subject/Observable就會觸發一次。如果你訂閱它,你會得到完整的細節清單。另一種方法是每當你從服務器得到響應時,將細節發送給你的組件。然後Subject/Observable會多次激發。如果你有大量的modyule,並且你寧願向用戶展示前10個,而不是讓他等到所有1000個已經加載(比方說),那麼這會很有用。 - batesiiic的代碼提供了實現這兩種方法的想法。 – Matt

1

您可能會使用Observable.zip來處理一次髮生的多個調用。這將需要讓您的getModyules調用返回一個Subject(某種),然後您可以在壓縮完成後再調用。

getModyules(): Observable<Modyule[]> { 
    var subject = new Subject<Modyule[]>(); 
    this.http.get(listOfModyuleIdsUrl + '.json') 
     .subscribe(result => { 
      this.getModyulesDetails(result) 
       .subscribe(modyules => { 
        subject.next(modyules); 
       }); 
     }, error => subject.error(error)); 

    return subject; 
} 

和細節打電話變得像(郵政編碼返回所有收益的集合,我相信會是Modyules[]你的情況):

private getModyulesDetails = (res: Response) => { // instance method for defining function so that the this.http below refers to the class. 
    let listOfModyules = res.json(); 
    let modyulesToReturn = []; 
    let calls = []; 

    for (let individualModyule of listOfModyules){ 
     calls.push(
      this.http.get(individualModyuleUrl + individualModyule.id + '.json') 
       .map((res: Response) => res.json())) 
     ); 
    } 

    var subject = new Subject<Modyules[]>(); 

    Observable.zip.apply(null, calls).subscribe((modyules: Modyules[]) => { 
     var modyulesToReturn = []; 
     modyules.forEach(modyule => { 
      let tempModyule = new Modyule; 
      //populate modyule data 
      modyulesToReturn.push(tempModyule); 
     } 
     subject.next(modyulesToReturn); 
    }); 

    return subject;  
} 

我做瀏覽器這些變化,所以我對語法錯誤表示歉意,但我認爲總體想法可以解決您要做的事情。

+0

我不確定我是否真的明白這一點 - 關於主題的文檔並沒有讓我對它更清晰,例如:http://xgrommx.github.io/rx-book/content/subjects/subject/index.html。我可以看到Subject是既是Observer也是Observable的東西,但是,由於沒有訂閱getModyules中的Subject,爲什麼getModyules不只是返回一個空主題,如果getModyulesDetails沒有及時完成... – theotherdy

+0

謝謝batesiiic。用@Matt討論過這個,聽起來好像這是正確的方法(對於10個左右的Modyules),它開始變得更有意義。然而,我無法解決的一點是如何獲得individualModyule.id,在第一次調用中讀入,與正確的modyule相關聯,正如在.zipped調用中返回的一樣......沒有提及到individualModyule.id json返回並且.zip中的電話可以以任何順序返回......我將在一分鐘內編輯我的問題以澄清我的意思。 – theotherdy

1

好吧,感謝@batesiiic,@Matt(我已經提出了你的答案)和一個有用的時間(相比兩個漫長的夜晚和我花在整個星期六的一個有用的時間這已經!)花了看Angular University,我有一個工作解決方案(迄今爲止!)。 .switchMap將兩個請求鏈接在一起,將Observable從第一個傳遞到第二個,@ batesiiic的精彩主題思想(以及forkJoin - 我無法使用zip工作),並意識到我可以在依賴項中獲得我的Modyule.id請求通過查看響應對象本身!

在modyule.component.ts在ngOnInit():

this.modyuleService.getModyules() 
     .switchMap(modyules =>this.modyuleService.getModyulesDetails(modyules)) 
     .subscribe(
      modyules => { 
       this.modyules = modyules 
       }, 
      error => this.errorMessage = <any>error); 

和module.service.ts:

getModyules(): Observable<Modyule[]> { 
    return this.http.get(this.listOfModyuleIdsUrl) 
     .map(this.initialiseModyules) 
     .catch(this.handleError);    
} 

private initialiseModyules(res: Response){ 
    let body = res.json(); 
    let modyulesToReturn = []; 
    for (let individualModyule of listOfModyules){ 
     let tempModyule = new Modyule; 
     tempModyule.id = individualModyule.id; 
     modyulesToReturn.push(tempModyule); 
     } 
    } 
    return modyulesToReturn; 
} 

getModyulesDetails (modyules:Modyule[]): Observable<Modyule[]> { 
    let calls = []; 

    for (let modyule of modyules){ 
     calls.push(
      this.http.get(this.individualModyuleUrl + modyule.id + '.json') 
      ); 
    } 

    var subject = new Subject<Modyule[]>(); 

    Observable.forkJoin(calls).subscribe((res: any) => { 
     for (let response of res){ 
      //Note this is a really very awkward way of matching modyule with a id assigned in getModyules (above) with the correct response from forkJoin (could come back in any order), by looking at the requested url from the response object 
      let foundModyule = modyules.find(modyule=> { 
       let modyuleUrl = this.individualModyuleUrl + modyule.id + '.json'; 
       return modyuleUrl === response.url; 
      }); 
      let bodyAsJson = JSON.parse(response._body); 
      foundModyule.name = res.name; 
      } 
     subject.next(modyules); 
    }); 

    return subject; 
} 

希望幫助別人。然而,仍然不禁要感到,在完成所有工作之前,不應該從多次調用返回細節,而不必訴諸免費提供所有.forkJoin或.zip,而您不再擁有這些內容與調用modyule的關係除了通過查看response.url ...

再次感謝您的所有幫助。

+1

對不起,我沒有迴應一段時間。我離開任何類型的電腦休假:)。真高興你做到了。作爲對Observable.zip中關於排序的問題之一的一個註釋,我相信它總是按照調用集合的順序向您發送響應。例如:調用A,B和C都被壓縮(按照傳入的數組的順序),結果將作爲響應數組[A,B,C]發送。我已經在一些非常依賴於響應數據正確順序的領域中使用了它。 – batesiiic

+1

完全沒有問題,謝謝 - 拉鍊聽起來很有用 - 我會再去看看我是否可以得到它的工作。任何人在看這個,但想要查詢具有相關請求的相同url也可能對@Thierry Templier在這個問題中提到的提取行爲感興趣http://stackoverflow.com/a/38814277/2235210。這似乎允許遞歸和遞歸條件,但我還沒有測試過。 – theotherdy

0

我不得不做一個類似的事情,在這之前我必須提出一個身份驗證令牌的請求,然後才能進行各種後續請求。

我想爲此公開一個「ApiService.Get(url)」,所有需要的工作都是使用隱藏在更高級別調用者中的令牌,但仍然只執行調用來獲取令牌一次。

我結束了這種事情......

export class ApiService { 
    private apiToken: string; 
    private baseHeaders: Headers; 
    private tokenObserver: ReplaySubject<Response>; 

    constructor(private http: Http) { 
    this.tokenObserver = null; 
    try { 
     // set token if saved in local storage 
     let apiData = JSON.parse(localStorage.getItem('apiData')); 
     if (apiData) { 
     this.apiToken = apiData.apiToken; 
     } 
     this.baseHeaders = new Headers({ 
     'Accept-encoding': 'gzip', 
     'API-Key': 'deadbeef-need-some-guid-123456789012', 
     }); 
    } catch(e) { 
     console.log(e); 
    } 
    } 

    private requestHeaders(): Headers { 
    if (this.apiToken) { 
     this.baseHeaders.set('Authorization', 'Bearer ' + this.apiToken); 
    } 
    return this.baseHeaders; 
    } 

    private getToken(): Observable<Response> { 
    if (this.tokenObserver == null) { 
     this.tokenObserver = new ReplaySubject<Response>(); 
     this.http.get('/newtoken', this.requestHeaders()) 
     .subscribe(
      this.tokenObserver.next, 
      this.tokenObserver.error, 
      this.tokenObserver.complete, 
     ); 
    } 
    return this.tokenObserver.asObservable(); 
    } 

    private isApiTokenValid():boolean { 
    console.log("Pretending to check token for validity..."); 
    return !!this.apiToken; 
    } 

    // Get is our publicly callable API service point 
    // 
    // We don't accept any options or headers because all 
    // interface to our API calls (other than auth) is through 
    // the URL.  
    public Get(url: string): Observable<Response> { 
    let observer: Subject<Response> = new Subject<Response>(); 
    try { 
     if (this.authTokenValid()) { 
     this.getRealURL(observer, url); 
     } else { 
     this.GetToken().subscribe(
      (v) => this.getRealURL(observer, url), 
      observer.error, 
      // ignore complete phase 
     ); 
     } 
    } catch(e) { 
     console.log("Get: error: " + e); 
    } 
    return observer.asObservable(); 
    } 

    private getRealURL(observer: Subject<Response>, url: string): void { 
    try { 
     this.http.get(url, {headers: this.requestHeaders()}) 
     .subscribe(
      observer.next, 
      observer.error, 
      observer.complete, 
     ); 
    } catch(e) { 
     console.log("GetRealURL: error: " + e); 
    } 
    } 
} 

有了這個地方,叫我的API歸結爲:

this.api.Get('/api/someurl') 
    .subscribe(this.handleSomeResponse) 
    .catch(this.handleSomeError);