通过在ng2中的可观察对象的依赖和并行请求
我在一个应用程序中显示有关'Modyules'(在课程中)必须通过两个http.gets(我不能控制apis不幸)的信息:通过在ng2中的可观察对象的依赖和并行请求
- 获取Modyule.ids列表 - 需要存储的ID
- 对于每个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://*.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之前所有响应?
如果一个请求依赖于另一个请求返回的数据(对于您的情况,您需要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
并同时接收更新。
如果您开始使用异步任务,则需要将该“设计”推送到您的组件。
您可能会使用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;
}
我做浏览器这些变化,所以我对语法错误表示歉意,但我认为总体想法可以解决您要做的事情。
我不确定我是否真的明白这一点 - 关于主题的文档并没有让我对它更清晰,例如:http://xgrommx.github.io/rx-book/content/subjects/subject/index.html。我可以看到Subject是既是Observer也是Observable的东西,但是,由于没有订阅getModyules中的Subject,为什么getModyules不只是返回一个空主题,如果getModyulesDetails没有及时完成... – theotherdy
谢谢batesiiic。用@Matt讨论过这个,听起来好像这是正确的方法(对于10个左右的Modyules),它开始变得更有意义。然而,我无法解决的一点是如何获得individualModyule.id,在第一次调用中读入,与正确的modyule相关联,正如在.zipped调用中返回的一样......没有提及到individualModyule.id json返回并且.zip中的电话可以以任何顺序返回......我将在一分钟内编辑我的问题以澄清我的意思。 – theotherdy
好吧,感谢@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 ...
再次感谢您的所有帮助。
对不起,我没有回应一段时间。我离开任何类型的电脑休假:)。真高兴你做到了。作为对Observable.zip中关于排序的问题之一的一个注释,我相信它总是按照调用集合的顺序向您发送响应。例如:调用A,B和C都被压缩(按照传入的数组的顺序),结果将作为响应数组[A,B,C]发送。我已经在一些非常依赖于响应数据正确顺序的领域中使用了它。 – batesiiic
完全没有问题,谢谢 - 拉链听起来很有用 - 我会再去看看我是否可以得到它的工作。任何人在看这个,但想要查询具有相关请求的相同url也可能对@Thierry Templier在这个问题中提到的提取行为感兴趣http://*.com/a/38814277/2235210。这似乎允许递归和递归条件,但我还没有测试过。 – theotherdy
我不得不做一个类似的事情,在这之前我必须提出一个身份验证令牌的请求,然后才能进行各种后续请求。
我想为此公开一个“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);
感谢@马特。我可能会误解,但这不是我通过在地图回调(在服务中)中调用getModyulesDetails中的细节来收集有效信息吗?我仍然不确定如何让getModyule等到getModyuleDetails(即使插入的地方你提出作为一个服务方法,它自己的订阅回调)已经返回...做一个.subscribe回调,其中包含另一个.subscribe回调有等到第二次订阅被调用? – theotherdy
谢谢@Matt。你的解释非常清楚。 batesiiic +例如http://*.com/a/38156976/2235210建议使用Rx.Subjects,但我真的不明白这是如何工作的。我是否正确地阅读你所说的,因为我应该避免单个组件中的这些依赖/链接请求,而是创建例如观察Observable getModyulesDetails方法的ModyuleDetailComponent?在这种情况下,我该如何让ModyuleDetailComponent知道ModyuleComponent已经完成获取Modyule.ids的列表,以便我可以例如@Input()他们到ModyuleDetailComponent? – theotherdy
这取决于。 batesiiic的方法是将所有东西都捆绑在一起,这样Subject/Observable就会触发一次。如果你订阅它,你会得到完整的细节清单。另一种方法是每当你从服务器得到响应时,将细节发送给你的组件。然后Subject/Observable会多次激发。如果你有大量的modyule,并且你宁愿向用户展示前10个,而不是让他等到所有1000个已经加载(比方说),那么这会很有用。 - batesiiic的代码提供了实现这两种方法的想法。 – Matt