I have a service which returns data in pages. The response to one page contains details on how to query for the next page.
My approach is to return the response data and then immediately concat a deferred call to the same observable sequence if there are more pages available.
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);
}
)
This must be the wrong approach, because within 2 seconds you get:
throw e;
^
RangeError: Maximum call stack size exceeded
at CompositeDisposablePrototype.dispose (/Users/me/node_modules/rx/dist/rx.all.js:654:51)
What is the correct approach to pagination?
Looking at the OP code, it is really the right method. Just need to make your mock service asynchronous to simulate real conditions. Here is a solution that avoids the stack exhaustion (I also made getPageFromServer
actually return a cold observable instead of requiring the caller to wrap it).
Note that if you really expect your service requests to complete synchronously in the real application and thus you need to ensure your code does not exhaust the stack when this happens, just have getPagedItems()
invoke the currentThread scheduler. The currentThread
scheduler schedules tasks using a trampoline to prevent recursive calls (and stack exhaustion). See the commented out line at the end of 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;
}
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With