根据响应递归组合 HTTP 结果

Recursively combining HTTP results based on response

有一个 API(https://panelapp.genomicsengland.co.uk/api/v1/panels/?page=1),我想使用我的 angular 应用程序的所有数据。问题是他们的 API 有分页,我想一次检索所有内容。

正如您在 API 上看到的那样,他们实际上在其响应中具有指向下一页的 "next" 属性。只要 "next" 属性不为空,我希望能够继续从 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);
})

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

为我服务(我 return 保证如果你愿意,你可以 return 观察)

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

如果我理解了您的意图,那么您希望使用 mergeMap 而不是 map。 Merge map 让您可以按顺序组合 observables,如下所示:

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

这样行吗?

已更新 请参阅@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)
      })
    )
  }

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

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

虽然这个问题已经得到解答,但我想提出另一种方法,使用 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