2016-07-29 16 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);