我有以下数据结构:
export class Repo {
id: number;
name: string;
contributors: Contributor[];
}
export class Contributor {
id: number;
login: string;
}
我正在使用Observable<Repo>
来获取所有repo数据,但我想在内部调用另一个observable Observable<Contributor>
以填充所有贡献者,然后才认为外部Repo
已完全发出。我不知道该怎么做。我有以下代码。
private repos: Repo[] = [];
getRepos(orgName: string): void {
const repoBuild: Repo[] = [];
this.githubService.getRepos(orgName).pipe(
// this here won't wait for all contributors to be resolved
map(repo => this.getRepoContributors(orgName, repo))
).subscribe(
repo => repoBuild.push(repo),
error => {
this.repos = [];
console.log(error);
},
() => this.repos = repoBuild
);
}
// fetches contributors data for the repo
private getRepoContributors(orgName: string, repo: Repo): Repo {
const contributors: Contributor[] = [];
repo.contributors = contributors;
this.githubService.getRepoContributors(orgName, repo.name)
.subscribe(
contributor => {
// add to the total collection of contributors for this repo
contributors.push(contributor);
},
error => console.log(error),
() => repo.contributors = contributors
);
return repo;
}
我承认我对Observable
的理解是有限的,我在这几个小时内一直在努力。我尝试在StackOverflow上找到适合我的东西,但我仍然找不到可行的解决方案。任何帮助将不胜感激!
(代码用Angular 5编写)
解决方案:
我在下面使用了@ joh04667建议,并最终得到了它的工作。我是这样做的:
getRepos(orgName: string): void {
const repoBuild: Repo[] = [];
this.githubService.getRepos(orgName).pipe(
// mergeMap() replaces `repo` with the result of the observable from `getRepoContributors`
mergeMap(repo => this.getRepoContributors(orgName, repo))
).subscribe(
repo => repoBuild.push(repo),
error => {
this.repos = [];
console.log(error);
},
() => this.repos = repoBuild
);
}
// fetches contributors data for the repo
private getRepoContributors(orgName: string, repo: Repo): Observable<Repo> {
repo.contributors = []; // make sure each repo has an empty array of contributors
return this.githubService.getRepoContributors(orgName, repo.name).pipe(
// tap() allows us to peek on each contributor and add them to the array of contributors
tap(contributor => {
// add to the total collection of contributors for this repo
repo.contributors.push(contributor);
}),
// only picks the last contributor and replaces him/her with the repo
last(
() => false,
() => repo,
repo
)
);
}
在我使用last()
的最后一部分中,我基本上告诉Observable
即使它将处理所有值,我只会消耗最后一个。最后一个是Contributor
类型,但我将其替换为默认值(repo
),这允许我将返回类型从Observable<Contributor>
更改为Observable<Repo>
我需要更高级别的Observable。
答案 0 :(得分:2)
这是一个很好的问题,对我来说,这是真正理解Observables全部功能的“重要一步”:高阶Observables,或者返回Observables的Observables。
您的情况非常适合mergeMap / flatMap
运营商:
getRepos(orgName: string): void {
const repoBuild: Repo[] = [];
this.githubService.getRepos(orgName).pipe(
// this will map the emissions of getRepos to getRepoContributors and return a single flattened Observable
mergeMap(repo => this.getRepoContributors(orgName, repo))
).subscribe(
repo => repoBuild.push(repo),
error => {
this.repos = [];
console.log(error);
},
() => this.repos = repoBuild
);
}
mergeMap
会将外部Observable (getRepos)
的排放映射到内部Observable(getRepoContributors
)并返回一个新的Observable,该Observable仅在内部Observable完成时发出。换句话说,它将从一个Observable传递到另一个Observable的值变平为一个简洁的可订阅数据流。
更高阶的Observable可能很难绕过你的脑袋,但这确实是Observables的真正力量所在。我强烈建议您在我关联的网站上查看其他运营商,例如switchMap
和concatMap
。当最大程度地使用时,可观察者会变得疯狂。
我误读了原始代码,并认为getRepoContributors
正在返回一个Observable。漫长的工作日让我油炸。我会在这里重构:
map
适用于在将值与mergeMap
合并之前更改值。 getRepoContributors
的前几行可以在那里完成。由于mergeMap
需要返回一个Observable,我们可以简化一下:
private repos: Repo[] = [];
getRepos(orgName: string): void {
const repoBuild: Repo[] = [];
this.githubService.getRepos(orgName).pipe(
map(repo => {
repo.contributors = [];
return repo;
}),
mergeMap(repo => this.githubService.getRepoContributors(orgName, repo.name)),
map(contributor => {
repo.contributors.push(contributor)
})
).subscribe(
repo => repoBuild.push(repo),
error => {
this.repos = [];
console.log(error);
},
() => this.repos = repoBuild
);
}
// don't need second function anymore
我们可以在流中map
期望值与运营商一起按顺序更改。