rxjs,递归调用api直到获取所有项目

时间:2018-04-28 17:07:26

标签: angular rxjs

我需要调用api并从端点获取所有项目。但是,我们一次只能获取100个项目。

响应如下:

{
    elements: Array<any>,
    totalCount: number,
}

,端点为/api/items?drop=${drop}&take=100drop用于分页。

我认为这将涉及扫描和takeWhile操作员某处。这就是我得到的:

    const subject = new Subject();
    const rec = subject.pipe(
        scan((acc: number, curr: number) => (curr + 100), 0),
        switchMap(drop => this.http.get(`api/items?drop=${drop}&take=100`)),
        takeWhile((r: any) => r.elements.length === 100),
        tap(r => subject.next())
    );

4 个答案:

答案 0 :(得分:2)

您不需要知道将返回多少元素,只需要返回多少元素:)

Observable.range(0, 1000000).pipe(
  concatMap(page => this.http.get(`api/items?drop=${page * take}&take=${take}`))
  takeWhile(results => results.elements.length === take)
)

起初我认为使用range作为源会堆积请求并破坏分页的目的,但concatMap会自动限制。

来自learnrxjs.io/operators/transformation/concatmap

  

注意concatMap和mergeMap之间的区别。因为concatMap在前一个完成之前没有订阅下一个observable,所以将首先发出源的值延迟2000ms。将此与立即订阅内部可观察量的mergeMap相比较,具有较小延迟(1000ms)的观察者将发射,接着是需要2000ms完成的观察者。

这是对 concatMap 的测试,显示在前一个结果发出之前不会调用getPage

const take = 100;
const getPage = (page) => {
  console.log('reading page', page);
  return Rx.Observable.of(page).delay(1000);
}

Rx.Observable.range(0,3)
  .concatMap(getPage)
  .subscribe(results => console.log('concatMap results', results));
<script src="https://cdnjs.cloudflare.com/ajax/libs/rxjs/5.5.10/Rx.js"></script>

mergeMap 对比,后者立即调用getPage

const take = 100;
const getPage = (page) => {
  console.log('reading page', page);
  return Rx.Observable.of(page).delay(1000);
}

Rx.Observable.range(0,3)
  .mergeMap(getPage)
  .subscribe(results => console.log('mergeMap results', results));
<script src="https://cdnjs.cloudflare.com/ajax/libs/rxjs/5.5.10/Rx.js"></script>

答案 1 :(得分:1)

似乎我通过使用外部变量让它工作,但它并不是真的很漂亮......

const take = 100;
const subject = new BehaviorSubject(0);
// var used for pagination
let i = 0;
const rec = subject.pipe(
    map((acc: number) => (acc * 100)),
    switchMap(drop => this.http.get(`api/items?drop=${drop}&take=${take}`)),
    map((r: any) => r.elements),
    tap(items=> {
        if (items.length === take)
            subject.next(++i);
    })
);
return rec;

答案 2 :(得分:1)

希望这会有所帮助:)

我会选择更简单的东西,订阅有点必要,但我想我们可以为了可读性而牺牲幻想。您应该考虑错误和重试,因为这显然只是快乐的路径

const subject = new Subject();
const createUrl = (drop = 0) => `api/items?drop=${drop}&take=100`
const fetchApi = url => Observable.fromPromise(fetch(url))

const requestItems = (pagination = 0) => {
    fetchApi(createUrl(pagination))
    .take(1)
    .subscribe(response => {
        if(response.elements.length === 100) {
            requestItems(pagination++)
        }
        subject.next(response)
    })
}

requestItems()

答案 3 :(得分:1)

阐述@cartant的建议,您可以满足您的需求,而不必依赖于主题,而是使用expand运算符,沿着以下代码段的方向

let counterOfItemsFetched = 0;
function api() {
    // console.log('counterOfItemsFetched', counterOfItemsFetched)
    return counterOfItemsFetched < 1000 ? 100 : 1;
}

function apiAsynCall() {
    return Observable.of(api()).delay(1000);
}

Observable.of(0).pipe(
    expand(() => apiAsynCall().pipe(tap(itemsFetched => counterOfItemsFetched = counterOfItemsFetched + itemsFetched))),
    filter(counter => counter > 0),
    tap(() => console.log(counterOfItemsFetched)),
    takeWhile(counter => counter ===  100),
)
.subscribe(
    null,
    null,
    () => console.log('counter value at the end', counterOfItemsFetched)
)