根据响应递归组合 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 的回答。这个答案被接受,但最好是指示答案
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
有一个 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 的回答。这个答案被接受,但最好是指示答案
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