等待Observable在Observable中完成

时间:2018-04-25 15:44:20

标签: angular typescript observable

我有以下数据结构:

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。

1 个答案:

答案 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的真正力量所在。我强烈建议您在我关联的网站上查看其他运营商,例如switchMapconcatMap。当最大程度地使用时,可观察者会变得疯狂。

修改

我误读了原始代码,并认为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期望值与运营商一起按顺序更改。

相关问题