问题
I am talking to some RESTful search endpoint that involves pagination. The query is triggered by user typing in a search field, and as a result it produces an Observable with N values, corresponding to the N result pages.
The code looks somewhat like the following:
function runQueries(queryObservable) {
return queryObservable
.debounceTime(500)
.distinctUntilChanged()
.switchMap(search);
}
function search(query) {
return Observable.create(observer => searchInto(observer, query, 0));
}
function searchInto(observer, query, start) {
runQuery(query, start).subscribe(result => {
observer.next(result);
if (hasMorePages(result)) {
searchInto(observer, query, start + 1);
} else {
observer.complete();
}
});
}
Now, the search requests can take a while and I don't want to retrieve all the pages if user changes the query.
Let's say the search return 3 pages, and user changes the query after one page is loaded. I want to see something like:
USER: types query A
CODE: loads page A1
USER: types query B
CODE: loads page B1
CODE: loads page B2
CODE: loads page B3
switchMap gets half of the job done. The resulting observable has the correct sequence: A1, B1, B2, B3. Great.
However, behind the scenes my recursive search is still running all the queries, putting unnecessary load on the server, network, etc. switchMap does discard the "obsolete" results, but it does not prevent the recursive function from doing its work to the end. In other words, it looks like:
USER: types query A
CODE: loads page A1 -> returned by search observable
USER: types query B
CODE: loads page A2 -> discarded by search observable
CODE: loads page B1 -> returned by search observable
CODE: loads page B2 -> returned by search observable
CODE: loads page A3 -> discarded by search observable
CODE: loads page B3 -> returned by search observable
The sequence of "A" and "B" is random (subject to race conditions), but it doesn't matter.
What am I doing wrong? What is the idiomatic solution to this?
回答1:
switchMap can only cancel an operation if you return an Observable that is cancelable. Since you are not returning the Subscription within Observable.create it can't cancel the in-flight operations.
As it stands using Observable.create is not really helping you in this case, I would suggest that you make use of the expand operator to perform recursive operations instead:
function runQueries(queryObservable) {
return queryObservable
.debounceTime(500)
.distinctUntilChanged()
.switchMap(search);
}
function search(query) {
//Kicks off the first query
return runQuery(query, 0)
//Uses the results of the first query to see if more queries should be made
.expand((result, idx) =>
//Continues to execute more queries until `hasMorePages` is false
hasMorePages(result) ?
runQuery(query, idx + 1) :
Observable.empty());
}
回答2:
flatMap is kind enough to unsubscribe from the Observable when it kicks off a new "stream". The producer can use the subscription status with observer.isUnsubscribed to stop its work:
function searchInto(observer, query, start) {
runQuery(query, start).subscribe(result => {
observer.next(result);
if (hasMorePages(result) && !observer.isUnsubscribed) {
searchInto(observer, query, start + 1);
} else {
observer.complete();
}
});
}
来源:https://stackoverflow.com/questions/39066087/rxjs-backpressure-with-switchmap-producing-n-values