简体   繁体   English

如何使用 RxJs 运算符进行递归 HTTP 调用?

[英]How to make recursive HTTP calls using RxJs operators?

I use the following method in odder to retrieve data by passing pageIndex (1) and pageSize (500) for each HTTP call.我在奇怪的情况下使用以下方法通过为每个 HTTP 调用传递pageIndex (1) 和pageSize (500) 来检索数据。

this.demoService.geList(1, 500).subscribe(data => {
    this.data = data.items;
});

The response has a property called isMore and I want to modify my method in odder to continue HTTP calls if isMore is true.响应有一个名为isMore的属性,如果isMore为真,我想更奇怪地修改我的方法以继续 HTTP 调用。 I also need to merge the returned values and finally return the accumulated values.我还需要合并返回的值,最后返回累积的值。

For example, assuming that there are 5000 records and until 10th HTTP call, the service returns true for isMore value.例如,假设有 5000 条记录,并且直到第 10 次 HTTP 调用,服务为isMore值返回 true。 After 10th HTTP call, it returns false and then this method sets this.data value with the merged 5000 records.在第 10 次 HTTP 调用后,它返回 false,然后此方法将this.data值设置为合并的 5000 条记录。 For this problem, should I use mergeMap or expand or another RxJs operator?对于这个问题,我应该使用mergeMap还是expand或另一个RxJs运算符? What is the proper way to solve this problem?解决这个问题的正确方法是什么?

Update: I use the following approach, but it does not merge the returned values and not increase the pageIndex.更新:我使用以下方法,但它不会合并返回的值,也不会增加 pageIndex。 For this reason it does not work (I tried to make some changes, but could not make it work).出于这个原因,它不起作用(我尝试进行一些更改,但无法使其起作用)。

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

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

Update III:更新三:

Finally I made it work by modifying Elisseo's approach as shown below.最后,我通过修改 Elisseo 的方法使其工作,如下所示。 Howeveri **I need to make it void and set this.data parameter in this getData() method.但是,**我需要将其设为 void 并在此getData()方法中设置this.data参数。 How can I do this?我怎样才能做到这一点?

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

I want to merge the following subscribe part to this approach but I cannot due to some errors eg "Property 'pipe' does not exist on type 'void'."我想将以下订阅部分合并到此方法,但由于某些错误,例如“'void' 类型上不存在属性'管道'”,我不能。

.subscribe((res: any) => {
    this.data = res;
});
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 NOTE: I updated pasing as argument pageIndex+1 as @mbojko suggest -before I wrote pageIndex++ stackblitz注意:在我写pageIndex++之前,我将作为参数 pageIndex+1 更新为@mbojko 建议

UPDATE 2更新 2

Using expand operator we need take account that we need feed the "recursive function" with an object with pageIndex -it's necesarry in our call- for this, when we make this.demoService.getList(data.pageIndex+1,10) we need "transform the result" adding a new property "pageIndex".使用扩展运算符,我们需要考虑到我们需要使用带有 pageIndex 的 object 来提供“递归函数” - 这是我们的调用所必需的 - 当我们制作this.demoService.getList(data.pageIndex+1,10)时,我们需要“转换结果”添加了一个新属性“pageIndex”。 for this we use "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
    )
  }

Well we can do a function like this:那么我们可以像这样做一个 function :

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

See that in this case we don't return else simple subscribe to the function and equal a variable this.data to res.data -it's the reason we don't need the last map看到在这种情况下,我们不返回 else 简单订阅 function 并将变量 this.data 等于 res.data -这就是我们不需要最后一个 map 的原因

Update 3 by Mrk Sef Mrk Sef更新 3

Finally, if you don't want your stream to emit intermittent values and you just want the final concatenated data, you can remove the data concatenation from expand , and use reduce afterward instead.最后,如果您不希望您的 stream 发出间歇值并且您只想要最终的连接数据,您可以从expand中删除数据连接,然后使用reduce代替。

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

It doesn't matter if you're total record change as long as api response give you the isMore flag.只要 api 响应为您提供 isMore 标志,您是否完全更改记录并不重要。

I'm skipping the part how to implement reducer action event i'm assuming you've already done that part.我跳过了如何实现减速器动作事件的部分,我假设你已经完成了那部分。 So i will just try to explain with pseudo codes.所以我将尝试用伪代码来解释。

You have a table or something like that with pagination data.你有一个带有分页数据的表格或类似的东西。 on intial state you can just create an loadModule effect or using this fn:在初始 state 上,您可以创建一个 loadModule 效果或使用此 fn:

getPaginationDataWithPageIndex(pageIndex = 1){ this.store.dispatch(new GetPaginationData({ pageIndex: pageIndex, dataSize: 500})); getPaginationDataWithPageIndex(pageIndex = 1){ this.store.dispatch(new GetPaginationData({ pageIndex: pageIndex, dataSize: 500})); } }

in your GetPaginationData effect在您的 GetPaginationData 效果中

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

I do not think recursion is the correct approach here:我不认为递归是正确的方法:

interval(0).pipe(
    map(count => this.demoService.getList(count + 1, 500)),
    takeWhile(reponse => response.isMore, true),
    reduce((acc, curr) => //reduce any way you like),
).subscribe();

This should make calls to your endpoint until the endpoint returns isMore === false.这应该调用您的端点,直到端点返回 isMore === false。 The beautiful thing about interval is that we get the count variable for free. interval 的美妙之处在于我们免费获得了 count 变量。

But if you are set on using recrsion, here is the rxjs-way to do that using the expand-operator (see the docs ).但是,如果您打算使用 recrsion,这里是使用 expand-operator 执行此操作的 rxjs 方式(请参阅文档)。 I find it slightly less readable, as it requires an if-else-construct which increases code complexity.我发现它的可读性稍差,因为它需要一个 if-else-construct,这会增加代码的复杂性。 Also the outer 'counter' variable just isn't optimal.此外,外部“计数器”变量也不是最佳的。

let index = 1;
this.demoService.geList(index, 500).pipe(
    expand(response => response.isMore ? this.demoService.geList(++index, 500) : empty()),
    reduce((acc, curr) => //reduce here)
).subscribe();

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM