首页 > 解决方案 > 完成for循环后如何完成订阅者?

问题描述

如果您向https://api.github.com/users/benawad/reposGET发出请求,您将获得用户拥有的所有存储库。至少这就是 API 端点预期返回的内容。问题在于 GitHub 将存储库的数量限制为 30 个。benawad

在此处输入图像描述

截至今天(2021 年 8 月 14 日),用户benawad拥有246 个存储库

为了克服上述问题,您应该发出 GET 请求,但需要添加一些额外的参数...... GitHub 已经对其 API 实现了分页。因此,您应该在 URL 中指定要检索的页面以及每页的存储库数量

我们的新GET请求应如下所示:

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

问题在于 GitHub 将每页的存储库数量限制为 100。因此我们的GET请求返回 100 个存储库,而不是用户当前benawad拥有的 246 个。

在此处输入图像描述

在我的 Angular 服务中,我实现了以下代码来从所有页面中检索所有存储库。

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

在我的组件中,我订阅了以下内容:

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

这种方法的问题是我无法控制 Observable 何时停止发出值。在我的组件中,我想在 Observable完成时触发一个函数。

我尝试了以下方法:

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

在我的组件中,我执行以下操作:

  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()唯一记录 46 个存储库。为什么会这样?也许我在获取所有 3 个页面之前完成了订阅者;但我正在调用.complete()订阅内部。我究竟做错了什么?提前致谢。

标签: angulartypescriptrxjsgithub-api

解决方案


您可以使用 RxJS 运算符和函数通过一次订阅来实现这一点,如下所示:

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);
          }, [])
        )
      );
    })
  );
}

然后在您的组件中,您可以订阅 observable,它将在获取所有 repos 后返回所有 repos:

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

推荐阅读