angular - Vänta på att Observable ska slutföras inom en Observable


Jag har följande datastruktur:

export class Repo {
    id: number;
    name: string;
    contributors: Contributor[];
}

export class Contributor {
    id: number;
    login: string;
}

Jag använder Observable<Repo> för att hämta all repo-data men jag vill internt anropa en annan observable Observable<Contributor> för att fylla alla bidragsgivare innan den yttre Repo anses vara helt utsänd. Jag är inte säker på hur man gör detta. Jag har följande kod.

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;
}

Jag erkänner att min förståelse för Observable är begränsad och jag har kämpat med detta i flera timmar. Jag försökte hitta något som fungerar för mig på StackOverflow men jag kunde fortfarande inte hitta en fungerande lösning. All hjälp skulle uppskattas!

(Koden är skriven i Angular 5)

Lösning:

Jag använde @joh04667:s förslag nedan och fick det till slut att fungera. Här är hur jag gjorde:

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
        )
    );
}

Här i den sista delen där jag använder last() berättar jag i princip för Observable att även om den kommer att bearbeta alla värden, kommer jag bara att konsumera det sista. Det sista är av typen Contributor men jag ersätter det med ett standardvärde (det repo) vilket gör att jag kan ändra returtypen från Observable<Contributor> till Observable<Repo> vilket är exakt vad jag behöver för det högre nivåns Observable.

Svar #1

Det här är en bra fråga, och för mig är detta det 'stora steget' till att verkligen förstå den fulla kraften hos Observables: högre ordningens Observables, eller Observables som returnerar Observables.

Din situation är perfekt för mergeMap/flatMap-operatorn:

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 kommer att mappa emissioner från en yttre Observable (getRepos) till en inre Observable (getRepoContributors) och returnera en ny Observable som endast emitterar när den inre Observablen har slutförts. Med andra ord, den plattar ut värden som skickas från en Observable till en annan till en enda koncis subscribbar dataström.

Observables på högre nivå kan vara svåra att greppa, men det är verkligen där den verkliga kraften i Observables ligger. Jag rekommenderar starkt att kolla in några andra operatorer som switchMap och concatMap på sidan jag länkade också. Observables kan bli otroligt kraftfulla när de används till sin fulla potential.

REDIGERA

Jag läste fel på den ursprungliga koden och trodde att getRepoContributors returnerade en Observable. En lång arbetsdag hade gjort mig utmattad. Jag kommer att refaktorera här:

map är bra för att ändra värdet innan det slås ihop med mergeMap. De första par raderna av getRepoContributors kan göras där. Eftersom mergeMap behöver att en Observable returneras till den, kan vi förenkla lite:

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

Vi kan map i strömmen och förvänta oss att värdet ändras i sekventiella ordningar med operatorn.

Relaterade artiklar