Javascript 使用RxJs将分页请求转换为可观察流
我有一个以页面形式返回数据的服务。对一个页面的响应包含有关如何查询下一个页面的详细信息 我的方法是返回响应数据,然后如果有更多的页面可用,则立即延迟调用相同的可观察序列Javascript 使用RxJs将分页请求转换为可观察流,javascript,rxjs,reactive-extensions-js,Javascript,Rxjs,Reactive Extensions Js,我有一个以页面形式返回数据的服务。对一个页面的响应包含有关如何查询下一个页面的详细信息 我的方法是返回响应数据,然后如果有更多的页面可用,则立即延迟调用相同的可观察序列 function getPageFromServer(index) { // return dummy data for testcase return {nextpage:index+1, data:[1,2,3]}; } function getPagedItems(index) { return Observ
function getPageFromServer(index) {
// return dummy data for testcase
return {nextpage:index+1, data:[1,2,3]};
}
function getPagedItems(index) {
return Observable.return(getPageFromServer(index))
.flatMap(function(response) {
if (response.nextpage !== null) {
return Observable.fromArray(response.data)
.concat(Observable.defer(function() {return getPagedItems(response.nextpage);}));
}
return Observable.fromArray(response.data);
});
}
getPagedItems(0).subscribe(
function(item) {
console.log(new Date(), item);
},
function(error) {
console.log(error);
}
)
这一定是错误的方法,因为在2秒内您会得到:
throw e;
^
RangeError: Maximum call stack size exceeded
at CompositeDisposablePrototype.dispose (/Users/me/node_modules/rx/dist/rx.all.js:654:51)
分页的正确方法是什么?编辑啊!我明白你面临的问题。一点尾部调用优化应该可以解决您的问题:
function mockGetPageAjaxCall(index) {
// return dummy data for testcase
return Promise.resolve({nextpage:index+1, data:[1,2,3]});
}
function getPageFromServer(index) {
return Observable.create(function(obs) {
mockGetPageAjaxCall(index).then(function(page) {
obs.onNext(page);
}).catch(function(err) {
obs.onError(err)
}).finally(function() {
obs.onCompleted();
});
});
}
function getPagedItems(index) {
return Observable.create(function(obs) {
// create a delegate to do the work
var disposable = new SerialDisposable();
var recur = function(index) {
disposable.setDisposable(getPageFromServer(index).retry().subscribe(function(page) {
obs.onNext(page.items);
if(page.nextpage === null) {
obs.onCompleted();
}
// call the delegate recursively
recur(page.nextpage);
}));
};
// call the delegate to start it
recur(0);
return disposable;
});
}
getPagedItems(0).subscribe(
function(item) {
console.log(new Date(), item);
},
function(error) {
console.log(error);
}
)
看看操作代码,它确实是正确的方法。只需要使您的模拟服务异步以模拟真实情况。下面是一个避免堆栈耗尽的解决方案(我还使
getPageFromServer
实际返回一个冷可观察对象,而不是要求调用方包装它)
请注意,如果您确实希望服务请求在实际应用程序中同步完成,因此需要确保发生这种情况时代码不会耗尽堆栈,只需调用getPagedItems()
即可。currentThread
调度器使用蹦床调度任务,以防止递归调用(和堆栈耗尽)。请参见getPagedItems
function getPageFromServer(index) {
// return dummy data asynchronously for testcase
// use timeout scheduler to force the result to be asynchronous like
// it would be for a real service request
return Rx.Observable.return({nextpage: index + 1, data: [1,2,3]}, Rx.Scheduler.timeout);
// for real request, if you are using jQuery, just use rxjs-jquery and return:
//return Rx.Observable.defer(function () { return $.ajaxAsObservable(...); });
}
function getPagedItems(index) {
var result = getPageFromServer(index)
.retry(3) // retry the call if it fails
.flatMap(function (response) {
var result = Rx.Observable.fromArray(response.data);
if (response.nextpage !== null) {
result = result.concat(getPagedItems(response.nextpage));
}
return result;
});
// If you think you will really satisfy requests synchronously, then instead
// of using the Rx.Scheduler.timeout in getPageFromServer(), you can
// use the currentThreadScheduler here to prevent the stack exhaustion...
// return result.observeOn(Rx.Scheduler.currentThread)
return result;
}
这里有一个更简洁的答案,没有任何递归。 它使用(~46 loc)将任何生成器转换为可观察的 它有一个自定义构建的next函数,它将在您的函数产生某些结果时发出数据 注:这本书值得一读
function getPagedItems({offset=0, limit=4}) {
paginatedQueryGenerator = function*(someParams offset, limit) {
let hasMore = true
while(hasMore) {
const results = yield YOUR_PROMISE_BASED_REQUEST(someParams, limit, offset)
hasMore = results && results.nextpage !== null
offset += limit
}
}
return ogen(paginatedQueryGenerator)(someParams, offset, limit)
}
另一个解决方案是使用retryWhen
getAllData() {
let page = 0;
let result = [];
const getOnePage = () => {
return of(0).pipe(mergeMap(() => getPaginatedData(page++)));
};
return getOnePage()
.pipe(
map(items => {
result = result.concat(items);
if (templates.length === PAGE_SIZE) {
throw 'get next page';
}
}),
retryWhen(e => e.pipe(
takeWhile(er => er === 'get next page'))
),
map(e => result)
)
.subscribe(items => {
console.log('here is all data', items);
});
}
当我测试这个(最多10页的退出路径)时,它返回1380个项目,然后停止。它怎么能停止呢?我已经用这段代码创建了一个JSFIDLE。它仍然抛出,但超出了最大调用堆栈大小。您仍然超出了最大调用堆栈大小。大约有430页返回。我认为递归可能不是这里最好的解决方案。谢谢你的解决方案。我希望用Rx构建一些东西,这样我就可以添加其他合成操作符,比如在出现异常时重试页面。因此,我将在接受之前等待,以防发布其他Rx解决方案。“嵌套订阅有问题,并且有点Rx反模式”。。。这是一个“反模式”的来源?它几乎覆盖了所有的核心源代码。(
.switch()
例如)我为此做了一个jsbin:谢谢你的回答,非常有用。这个jsbin上的输出对我来说更容易理解: