递归和可观察的RxJ

时间:2020-02-28 16:20:07

标签: javascript recursion rxjs observable reactive-programming

我正在内部进行分页和可观察的流。 分页是使用游标实现的,并使用递归实现总计数。

我能够使用下面的代码observer.next(searches);来发射每个页面,顺便说一下,我想只使用可观察的但没有承诺,但是我不能使用RxJs运算符表示递归。

有什么建议吗?

    const search = id =>
      new Observable(observer => { recursePages(id, observer) })

    const recursePages = (id, observer, processed, searchAfter) => {
      httpService.post(
        "http://service.com/search",
        {
          size: 50,
          ...searchAfter ? { search_after: searchAfter } : null,
          id,
        })
        .toPromise() // httpService.post returns an Observable<AxiosResponse>
        .then(res => {
          const body = res.data;
          const searches = body.data.hits.map(search => ({ data: search.data, cursor: search.id }));
          observer.next(searches);
          const totalProcessed = processed + searches.length;
          if (totalProcessed < body.data.total) {
            return recursePages(id, observer, totalProcessed, searches[searches.length - 1].cursor);
          }
          observer.complete();
        })
    }

    // General Observer
    incomingMessages.pipe(
        flatMap(msg => search(JSON.parse(msg.content.toString()))),
        concatAll(),
    ).subscribe(console.log),    


1 个答案:

答案 0 :(得分:0)

这些方法将递归地收集所有页面,并将它们以数组的形式发出。然后可以使用from来流式传输页面,如下所示:

// break this out to clean up functions
const performSearch = (id, searchAfter?) => {
  return httpService.post(
    "http://service.com/search",
    {
      size: 50,
      ...searchAfter ? { search_after: searchAfter } : null,
      id,
    });
}

// main recursion
const _search = (id, processed, searchAfter?) => {
  return performSearch(id, searchAfter).pipe( // get page
    switchMap(res => {
      const body = res.data;
      const searches = body.data.hits.map(search => ({ data: search.data, cursor: search.id }));
      const totalProcessed = processed + searches.length;
      if (totalProcessed < body.total) {
        // if not done, recurse and get next page
        return _search(id, totalProcessed, searches[searches.length - 1].cursor).pipe(
          // attach recursed pages
          map(nextPages => [searches].concat(nextPages)
        );
      }
      // if we're done just return the page
      return of([searches]);
    })
  )
}

// entry point
// switch into from to emit pages one by one
const search = id => _search(id, 0).pipe(switchMap(pages => from(pages))

例如,如果您真正需要的是所有页面都在被提取之前一一发出,那么您可以在页面1可用时立即显示它,而不是等到页面2+之后再显示。进行一些调整。让我知道。

编辑:此方法将一一发出

const _search = (id, processed, searchAfter?) => {
  return performSearch(id, searchAfter).pipe( // get page
    switchMap(res => {
      const body = res.data;
      const searches = body.data.hits.map(search => ({ data: search.data, cursor: search.id }));
      const totalProcessed = processed + searches.length;
      if (totalProcessed < body.total) {
        // if not done, concat current page with recursive call for next page
        return concat(
          of(searches),
          _search(id, totalProcessed, searches[searches.length - 1].cursor)
        );
      }
      // if we're done just return the page
      return of(searches);
    })
  )
}
const search = id => _search(id, 0)

您最终会看到类似以下的结构:

concat(
  post$(page1),
  concat(
    post$(page2),
    concat(
      post$(page3),
      post$(page4)
    )
  )
)

并且由于嵌套的concat()操作简化为扁平化的结构,因此该结构将简化为:

concat(post$(page1), post$(page2), post$(page3), post$(page4))

这就是您要执行的操作,并且请求按顺序运行。

似乎也可以按照@NickL的注释,expand可以解决问题,

search = (id) => {
  let totalProcessed = 0;
  return performSearch(id).pipe(
    expand(res => {
      const body = res.data;
      const searches = body.data.hits.map(search => ({ data: search.data, cursor: search.id }));
      totalProcessed += searches.length;
      if (totalProcessed < body.data.total) {
        // not done, keep expanding
        return performSearch(id, searches[searches.length - 1].cursor);
      }
      return EMPTY; // break with EMPTY
    })
  )
}

尽管我以前从未使用过expand,但这是基于对它的一些非常有限的测试,但是我可以肯定这是可行的。

如果您愿意,这两种方法都可以使用reduce(或scan)运算符收集结果:

search(id).pipe(reduce((all, page) => all.concat(page), []))