angular - Hur man avslutar en prenumerant när en for-loop är klar?


Om du gör en GET-förfrågan till https://api.github.com/users/benawad/repos kommer du att få alla de repositorier som användaren benawad har. Åtminstone är det vad den API-slutpunkten förväntas returnera. Problemet är att GitHub begränsar antalet repositorier till 30.

Från och med idag (14/08/2021) har användaren benawad 246 repositorier.

För att övervinna det ovan nämnda problemet bör du göra en GET-begäran men med några ytterligare parametrar... GitHub har implementerat paginering i sitt API. Så, i URL:en bör du specificera vilken sidnummer du vill hämta och antalet repository per sida.

Vår nya GET-förfrågan bör se ut ungefär så här:

https://api.github.com/users/benawad/repos?page=1&per_page=1000

Problemet med detta är att GitHub har begränsat antalet repository per sida till 100. Så vår GET-förfrågan returnerar 100 repos och inte de 246 som användaren benawad för närvarande har.

I min Angular-tjänst har jag implementerat följande kod för att hämta alla repos från alla sidor.

  public getUserRepos(user: string): Observable<RepositoryI[]> {
    return new Observable((subscriber: Subscriber<RepositoryI[]>) => {
      this.getUserData(user).subscribe((data: UserI) => {
        //public_repos is the amt of repos the user has
        const pages: number = Math.ceil(data.public_repos / 100);

        for (let i = 1; i <= pages; i++) {
          this.http
            .get(
              `https://api.github.com/users/${user}/repos?page=${i}&per_page=100`
            )
            .subscribe((data: RepositoryI[]) => {
              subscriber.next(data);
            });
        }
      });
    });
  }

Och i min komponent prenumererar jag med följande:

  this.userService.getUserRepos(id).subscribe((repos)=>{
    this.repositories.push(...repos);
  })

Problemet med detta tillvägagångssätt är att jag inte har någon kontroll över när Observablen har slutat avge värden. I min komponent skulle jag vilja trigga en funktion när Observablen är klar.

Jag har försökt följande:

  public getUserRepos(user: string): Observable<RepositoryI[]> {
    return new Observable((subscriber: Subscriber<RepositoryI[]>) => {
      this.getUserData(user).subscribe((data: UserI) => {
        const pages: number = Math.ceil(data.public_repos / 100);

        for (let i = 1; i <= pages; i++) {
          this.http
            .get(
              `https://api.github.com/users/${user}/repos?page=${i}&per_page=100`
            )
            .subscribe((data: RepositoryI[]) => {
              subscriber.next(data);
              // If the for loop is complete -> complete the subscriber
              if(pages == i) subscriber.complete();
            });
        }
      });
    });
  }

Och i min komponent gör jag följande:

  this.userService.getUserRepos(id).subscribe(
    (repos) => {
      this.repositories.push(...repos);
    },
    (err) => {
      console.log(err);
    },
    () => {
      // When the observable is complete:
      
      console.log(this.repositories); // This only logs 46 repositores, it should log 246
      // I want to trigger some function here
    }
  );

console.log() loggar bara 46 repository. Varför händer detta? Kanske avslutar jag subscribern innan den kan hämta alla 3 sidor; men jag kallar på .complete() inuti subscribern. Vad gör jag fel? Tack på förhand.

Svar #1

Du kan uppnå det med en prenumeration genom att använda RxJS-operatörer och funktioner, som följande:

public getUserRepos(user: string): Observable<RepositoryI[]> {
  // will return one observable that contains all the repos after concating them to each other:
  return this.getUserData(user).pipe(
    switchMap((data: UserI) => {
      //public_repos is the amt of repos the user has
      const pages: number = Math.ceil(data.public_repos / 100);

      // forkJoin will emit the result as (RepositoryI[][]) once all the sub-observables are completed:
      return forkJoin(
        Array.from(new Array(pages)).map((_, page) =>
          this.http.get<RepositoryI[]>(
            `https://api.github.com/users/${user}/repos?page=${page + 1}&per_page=100`
          )
        )
      ).pipe(
        // will reduce the RepositoryI[][] to be RepositoryI[] after concating them to each other:
        map((res) =>
          res.reduce((acc, value) => {
            return acc.concat(value);
          }, [])
        )
      );
    })
  );
}

Sedan kan du i din komponent prenumerera på det observerbara som kommer att returnera alla repos efter att ha hämtat dem alla:

  this.userService.getUserRepos(id).subscribe((repos) => {
    this.repositories = repos;
    // You can trigger the function that you need here...
  });

Svar #2

          let subs = interval(1000).subscribe(() => {
          this.http
            .get(
              `https://api.github.com/users/${user}/repos?page=${i}&per_page=100`
            )
            .subscribe((data: RepositoryI[]) => {
              subscriber.next(data);
              if(!data) { // to check if data is null
                subs = null;  // if null, cancel the interval subscription
                return;  // and return (not continue the subscription)
              }
            });
          })
        }

På detta sätt lagrar du prenumerationen och efter att ha kontrollerat om det finns data, avslutar du prenumerationen (stoppar).

Vänligen låt mig veta om detta fungerar.

Relaterade artiklar