Рекурсивное объединение результатов HTTP на основе ответа - PullRequest
1 голос
/ 27 июня 2019

Есть API (https://panelapp.genomicsengland.co.uk/api/v1/panels/?page=1), для которого я хочу использовать все данные для своих угловых приложений. Проблема в том, что их API имеет нумерацию страниц, и я хочу получить весь контент сразу.

Как вы можете видеть в API, на самом деле они имеют атрибут "next" в своем ответе, который указывает на следующую страницу. Я хочу иметь возможность продолжать запрашивать у API до тех пор, пока атрибут "next" не равен NULL, а затем объединить все их ответы в один.

Я пытался использовать рекурсив, но к тому времени, как он достигнет 2-го цикла, я получаю неопределенное значение. Я думаю, что это потому, что асинхронный запрос, следовательно, я получаю неопределенным.

Ниже мой код

@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 ]

3 голосов
/ 27 июня 2019

Хотя на этот вопрос уже дан ответ, я хотел бы предложить другой подход с использованием expand operator [https://rxjs -dev.firebaseapp.com / api / operator / 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);
  }

см. Рабочий стекблиц

1 голос
/ 27 июня 2019

Это легко сделать в 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);
}
1 голос
/ 27 июня 2019

Обновлено см. Ответ @ 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)
      })
    )
  }
0 голосов
/ 27 июня 2019

Если я понял, что вы пытаетесь сделать, вы хотите использовать 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;
        }
      })
    );
}

Это работает?

0 голосов
/ 27 июня 2019

Я реализовал аналогичную реализацию в одном из моих проектов.

В моем сервисе (я возвращаю обещание, что вы можете вернуть наблюдаемое, если хотите)

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