angular - Vent på at Observable fullfører innenfor en Observable


Jeg har følgende datastruktur:

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

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

Jeg bruker Observable<Repo> for å hente alle repo-data, men jeg vil internt kalle en annen observable Observable<Contributor> for å fylle ut alle bidragsytere før den ytre Repo anses som fullstendig sendt. Jeg er ikke sikker på hvordan jeg skal gjøre dette. Jeg har følgende kode.

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

Jeg innrømmer at min forståelse av Observable's er begrenset, og jeg har slitt med dette i flere timer. Jeg prøvde å finne noe som fungerer for meg på StackOverflow, men jeg kunne fortsatt ikke finne en løsning som fungerer. All hjelp vil bli verdsatt!

(Koden er skrevet i Angular 5)

Løsning:

Jeg brukte @joh04667 sitt forslag nedenfor og fikk det til å fungere til slutt. Slik gjorde jeg det:

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

Her i den siste delen hvor jeg bruker last() forteller jeg i praksis Observable at selv om den vil behandle alle verdiene, vil jeg bare konsumere den siste. Den siste er av typen Contributor, men jeg erstatter den med en standardverdi ( repo ), som lar meg endre returtypen fra Observable<Contributor> til Observable<Repo>, noe som er nøyaktig det jeg trenger for det høyere nivået Observable.

Svar #1

Dette er et godt spørsmål, og for meg er dette det 'store steget' for virkelig å forstå hele kraften til Observables: høyereordens Observables, eller Observables som returnerer Observables.

Din situasjon er perfekt for mergeMap/flatMap-operatoren:

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 vil mappe utslippene fra en ytre Observable (getRepos) til en indre Observable (getRepoContributors) og returnere en ny Observable som bare sender ut når den indre Observable fullføres. Med andre ord, den flater ut verdier som overføres fra én Observable til en annen til én konsis dataflyt som kan abonneres på.

Observables på høyere ordre kan være vanskelige å forstå, men det er virkelig der den ekte kraften til Observables ligger. Jeg anbefaler sterkt å sjekke ut noen andre operatører som switchMap og concatMap på nettstedet jeg lenket til også. Observables kan bli utrolig kraftige når de brukes til sin fulle utstrekning.

REDIGER

Jeg leste den opprinnelige koden feil og trodde getRepoContributors returnerte en Observable. En lang arbeidsdag hadde gjort meg utslitt. Jeg vil refaktorere her:

map er bra for å endre verdien før man slår den sammen med mergeMap. De første par linjene av getRepoContributors kan gjøres der. Siden mergeMap trenger at et Observable returneres til det, kan vi forenkle litt:

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 og forvente at verdien endres i sekvensielle rekkefølger med operatøren.

Relaterte artikler