我正在内部进行分页和可观察的流。 分页是使用游标实现的,并使用递归实现总计数。
我能够使用下面的代码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),
答案 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), []))