javascript - Hur man gör rekursiva HTTP-anrop med RxJs-operatörer?
Jag använder följande metod för att hämta data genom att skicka pageIndex (1) och pageSize (500) för varje HTTP-anrop.
this.demoService.geList(1, 500).subscribe(data => {
this.data = data.items;
});
Svaret har en egenskap som kallas isMore och jag vill ändra min metod för att fortsätta HTTP-anrop om isMore är sant. Jag behöver också slå samman de returnerade värdena och slutligen returnera de ackumulerade värdena.
Till exempel, om vi antar att det finns 5000 poster och fram till den 10:e HTTP-anropet returnerar tjänsten true för värdet isMore. Efter det 10:e HTTP-anropet returnerar det false och sedan sätter denna metod värdet på this.data med de sammanslagna 5000 posterna. För detta problem, ska jag använda mergeMap eller expand eller en annan RxJs-operator? Vad är det rätta sättet att lösa detta problem?
Uppdatering: Jag använder följande tillvägagångssätt, men det slår inte ihop de returnerade värdena och ökar inte pageIndex. Av den anledningen fungerar det inte (jag försökte göra några ändringar, men kunde inte få det att fungera).
let pageIndex = 0;
this.demoService.geList(pageIndex+1, 500).pipe(
expand((data) => {
if(data.isComplete) {
return of(EMPTY);
} else {
return this.demoService.geList(pageIndex+1, 500);
}
})
).subscribe((data) => {
//your logic here
});
Uppdatering II: :
of({
isMore : true,
pageIndex: 0,
items: []
}).pipe(
expand(data => demoService.geList(data.pageIndex+1, 100)
.pipe(
map(newData => ({...newData, pageIndex: data.pageIndex+1}))
)),
// takeWhile(data => data.isMore), //when using this, it does not work if the total record is less than 100
takeWhile(data => (data.isMore || data.pageIndex === 1)), // when using this, it causing +1 extra HTTP call unnecessarily
map(data => data.items),
reduce((acc, items) => ([...acc, ...items]))
)
.subscribe(data => {
this.data = data;
});
Uppdatering III: :
Slutligen fick jag det att fungera genom att ändra Elisseos metod som visas nedan. Dock **behöver jag göra den void och sätta this.data parametern i denna getData() metod. Hur kan jag göra detta?
getData(pageIndex, pageSize) {
return this.demoService.geList(pageIndex, pageSize).pipe(
switchMap((data: any) => {
if (data.isMore) {
return this.getData(pageIndex+1, pageSize).pipe(
map((res: any) => ({ items: [...data.items, ...res.items] }))
);
}
return of(data);
})
);
}
Jag vill slå ihop följande prenumerationsdel med detta tillvägagångssätt men jag kan inte på grund av vissa fel, t.ex. "Property 'pipe' does not exist on type 'void'."
.subscribe((res: any) => {
this.data = res;
});
Svar #1
getData(pageIndex, pageSize) {
return this.demoService.getList(pageIndex, pageSize).pipe(
switchMap((data: any) => {
if (!data.isCompleted) {
return this.getData(pageIndex+1, pageSize).pipe(
map((res: any) => ({ data: [...data.data, ...res.data] }))
);
}
return of(data);
})
);
}
stackblitz OBS: Jag uppdaterade pasing som argument pageIndex+1 som @mbojko föreslog - innan skrev jag pageIndex++
UPPDATERING 2
När vi använder expand-operatorn måste vi ta hänsyn till att vi behöver mata den "rekursiva funktionen" med ett objekt med pageIndex - det är nödvändigt i vårt anrop - för detta, när vi gör this.demoService.getList(data.pageIndex+1,10) behöver vi "transformera resultatet" genom att lägga till en ny egenskap "pageIndex". För detta använder vi "map"
getData() {
//see that initial we create "on fly" an object with properties: pageIndex,data and isCompleted
return of({
pageIndex:1,
data:[],
isCompleted:false
}).pipe(
expand((data: any) => {
return this.demoService.getList(data.pageIndex,10).pipe(
//here we use map to create "on fly" and object
map((x:any)=>({
pageIndex:data.pageIndex+1, //<--pageIndex the pageIndex +1
data:[...data.data,...x.data], //<--we concatenate the data using spread operator
isCompleted:x.isCompleted})) //<--isCompleted the value
)
}),
takeWhile((data: any) => !data.isCompleted,true), //<--a take while
//IMPORTANT, use "true" to take account the last call also
map(res=>res.data) //finally is we only want the "data"
//we use map to return only this property
)
}
Nåväl, vi kan göra en funktion som denna:
getData() {
of({pageIndex:1,data:[],isCompleted:false}).pipe(
expand((data: any) => {
return this.demoService.getList(data.pageIndex,10).pipe(
tap(x=>{console.log(x)}),
map((x:any)=>({
pageIndex:data.pageIndex+1,
data:[...data.data,...x.data],
isComplete:x.isComplete}))
)
}),
takeWhile((data: any) => !data.isComplete,true), //<--don't forget the ",true"
).subscribe(res=>{
this.data=res.data
})
}
Se att i det här fallet returnerar vi inte annat, vi prenumererar helt enkelt på funktionen och sätter en variabel denna.data till res.data - det är anledningen till att vi inte behöver den sista mapen
Uppdatering 3 av Mrk Sef
Slutligen, om du inte vill att din ström ska sända intermittenta värden och du bara vill ha den slutliga sammanfogade datan, kan du ta bort datakombinationen från expand och använda reduce efteråt istället.
getData() {
of({
pageIndex: 1,
data: [],
isCompleted: false
})
.pipe(
expand((prevResponse: any) => this.demoService.getList(prevResponse.pageIndex, 10).pipe(
map((nextResponse: any) => ({
...nextResponse,
pageIndex: prevResponse.pageIndex + 1
}))
)
),
takeWhile((response: any) => !response.isCompleted, true),
// Keep concatenting each new array (data.items) until the stream
// completes, then emit them all at once
reduce((acc: any, data: any) => {
return [...acc, ...data.data];
}, [])
)
.subscribe(items => {
this.data=items;
});
}
Svar #2
Det spelar ingen roll om du är total registerändring så länge API-svaret ger dig isMore-flaggan.
Jag hoppar över delen om hur man implementerar reducer action-event, jag antar att du redan har gjort den delen. Så jag kommer bara försöka förklara med pseudokoder.
Du har ett bord eller något liknande med pagineringsdata. I initialt tillstånd kan du bara skapa ett loadModule-effekt eller använda denna fn:
getPaginationDataWithPageIndex(pageIndex=1){ this.store.dispatch(new GetPaginationData({ pageIndex: pageIndex, dataSize: 500})); }
i din GetPaginationData-effekt
... map(action => {
return apicall.pipe(map((response)=> {
if(response.isMore){
return new updateState({data:response.data, isMore: responseisMore})
} else {
return new updateState({isMore: response.isMore}),
}
}})
`
all you have to left is subscribing store in your .ts if isMore is false you will not display the next page button. and on your nextButton or prevButton's click method you should have to just dispatch the action with pageIndex
