根据响应递归组合HTTP结果

时间:2019-06-27 07:40:23

标签: angular recursion rxjs angular-httpclient

有一个API(https://panelapp.genomicsengland.co.uk/api/v1/panels/?page=1),我想将所有数据消耗到我的角度应用程序中。问题在于他们的API具有分页功能,我想一次检索所有内容。

正如您在API上所看到的,它们实际上在响应中具有指向下一页的“ next”属性。我希望只要“ next”属性不为null,就可以继续向API发出请求,然后将其所有响应合并为一个。

我尝试使用递归,但是当它到达第二个循环时,我得到了不确定的值。我的猜测是,这是因为异步请求,因此我未定义。

下面是我的代码

@Injectable()
export class GenomicsEnglandService {
    panels = [];
    constructor(private http: HttpClient) {
    }

    getPanels(url): Observable<any>{
        const headers = new HttpHeaders()
        .append('Content-Type', 'application/json')
        .append('Accept', '*/*');

        return this.http.get(url, {headers: headers})
            .map((data) => {
                panels = panels.concat(data.results);
                if(data.next){
                    this.getPanels(data.next);
                }else{
                    return panels;
                }
            })
            .catch((e) => {
                Raven.captureMessage("GENOMICS ENGLAND ERROR: " + JSON.stringify(e));
                return of([]);
            });

    }

}

然后从我刚刚调用的组件

this.GenomicsEnglandService.getPanels('https://panelapp.genomicsengland.co.uk/api/v1/panels/?page=1').subscribe(data => {
  console.log(data);
})

5 个答案:

答案 0 :(得分:3)

尽管已经回答了这个问题,但我想提出另一种方法,使用expand运算符[https://rxjs-dev.firebaseapp.com/api/operators/expand]expand运算符用于此类递归目的:

getResult() {

    const url = "https://panelapp.genomicsengland.co.uk/api/v1/panels/";

    return this.getResponse(url)
                    .pipe(
                      expand((res: any) => this.getResponse(res.next)),
                      takeWhile((res: any) => res.next, true),
                      concatMap((res: any) => res.results),
                      reduce((acc, val) => {
                        acc.push(val);
                        return acc;
                      }, []),
                      tap(_ => {
                        console.log(_);
                        this.loading = false;
                      })
                    )

  }

  getResponse(url) {
    return this.httpClient.get(url);
  }

查看有效的stackblitz

答案 1 :(得分:1)

已更新,请参见@ user2216584的答案。这个答案是可以接受的,但最好是指示答案

请参阅stackblitz

constructor(private httpClient:HttpClient){}
  ngOnInit()
  {

    this.getHttp('https://panelapp.genomicsengland.co.uk/api/v1/panels/',null).subscribe(res=>{
      this.data=res
    })

  }

  getHttp(url,fullData:any[]):Observable<any[]>
  {
    fullData=fullData || []
    return this.httpClient.get(url).pipe(
      switchMap((data:any)=>{
        fullData=fullData.concat(data.results);
        return !data.next? of(fullData):
               this.getHttp(data.next,fullData)
      })
    )
  }

答案 2 :(得分:1)

这可以通过使用expand运算符在rxjs中轻松完成:

import {empty, Observable} from 'rxjs';
import {expand, map, reduce} from 'rxjs/operators';

export interface PanelResponse {
  results: object[];
  next: string|null;
}

@Injectable()
export class Service {
  private readonly baseUrl = 'https://panelapp.genomicsengland.co.uk/api/v1/panels/';

  constructor(private http: HttpClient) {
  }

  getPanels(): Observable<object[]>{
    return this.get(this.baseUrl).pipe(
      expand(({next}) => next ? get(next) : empty()),
      map(({results}) => results),
      // if you want the observable to emit 1 value everytime that
      // a page is fetched, use `scan` instead of `reduce`
      reduce((acc, val) => acc.concat(val), new Array<object>()),
    );
  }

  private get(url:string>):Observable<PanelResponse> => this.http.get<PanelResponse>(url);
}

答案 3 :(得分:0)

如果我了解您要执行的操作,则希望使用mergeMap而不是map。合并映射使您可以按顺序合并可观察对象,如下所示:

getPanels(url): Observable<any> {
  return this.http.get(url)
    .pipe(
      mergeMap(data => {
        panels = panels.concat(data.results);
        if(data.next) {
          return this.getPanels(data.next);
        } else {
          return panels;
        }
      })
    );
}

行得通吗?

答案 4 :(得分:-1)

我在一个项目中也做了类似的实现。

在我的服务中(我答应您可以返回观察到的内容)

  getDataHttp(url) {
    return this.http.get(url).toPromise();
  }

  getData(url) {
    let response = {};
    let data = [];
    return new Promise(async (resolve, reject) => {
      try {
        do {
          response = await this.getDataHttp(url);
          data.push(response);
        } while(response['next']); 
        resolve(data);
      } catch(err) {
        reject(err);
      }
    })
  }

在我的组件中

this.service.getData(url).then((response) => {
  console.log(response);
}).catch(err => console.log(err))