繁体   English   中英

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

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

我在奇怪的情况下使用以下方法通过为每个 HTTP 调用传递pageIndex (1) 和pageSize (500) 来检索数据。

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

响应有一个名为isMore的属性,如果isMore为真,我想更奇怪地修改我的方法以继续 HTTP 调用。 我还需要合并返回的值,最后返回累积的值。

例如,假设有 5000 条记录,并且直到第 10 次 HTTP 调用,服务为isMore值返回 true。 在第 10 次 HTTP 调用后,它返回 false,然后此方法将this.data值设置为合并的 5000 条记录。 对于这个问题,我应该使用mergeMap还是expand或另一个RxJs运算符? 解决这个问题的正确方法是什么?

更新:我使用以下方法,但它不会合并返回的值,也不会增加 pageIndex。 出于这个原因,它不起作用(我尝试进行一些更改,但无法使其起作用)。

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

更新二:

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

更新三:

最后,我通过修改 Elisseo 的方法使其工作,如下所示。 但是,**我需要将其设为 void 并在此getData()方法中设置this.data参数。 我怎样才能做到这一点?

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

我想将以下订阅部分合并到此方法,但由于某些错误,例如“'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注意:在我写pageIndex++之前,我将作为参数 pageIndex+1 更新为@mbojko 建议

更新 2

使用扩展运算符,我们需要考虑到我们需要使用带有 pageIndex 的 object 来提供“递归函数” - 这是我们的调用所必需的 - 当我们制作this.demoService.getList(data.pageIndex+1,10)时,我们需要“转换结果”添加了一个新属性“pageIndex”。 为此,我们使用“地图”

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

那么我们可以像这样做一个 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
    })
  }

看到在这种情况下,我们不返回 else 简单订阅 function 并将变量 this.data 等于 res.data -这就是我们不需要最后一个 map 的原因

Mrk Sef更新 3

最后,如果您不希望您的 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;
      });
  }

只要 api 响应为您提供 isMore 标志,您是否完全更改记录并不重要。

我跳过了如何实现减速器动作事件的部分,我假设你已经完成了那部分。 所以我将尝试用伪代码来解释。

你有一个带有分页数据的表格或类似的东西。 在初始 state 上,您可以创建一个 loadModule 效果或使用此 fn:

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

在您的 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

我不认为递归是正确的方法:

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

这应该调用您的端点,直到端点返回 isMore === false。 interval 的美妙之处在于我们免费获得了 count 变量。

但是,如果您打算使用 recrsion,这里是使用 expand-operator 执行此操作的 rxjs 方式(请参阅文档)。 我发现它的可读性稍差,因为它需要一个 if-else-construct,这会增加代码的复杂性。 此外,外部“计数器”变量也不是最佳的。

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