So I have use case where I update the api request when the map is moved - but it could generate several rapid fire requests with small map movements - and I want to cancel all the inflight requests except for the last one. I can use debounce to only send requests after a delay. However I still want to cancel any old requests if they happen to still be in process.
const fetchNearbyStoresEpic = action$ =>
action$.ofType(FETCH_NEARBY_STORES)
.debounceTime(500)
.switchMap(action =>
db.collection('stores')
.where('location', '<=', action.payload.max).
.where('location', '>=', action.payload.min)
.map(response => fetchNearbyStoresFulfilled(response))
.takeUntil(action$.ofType(FETCH_STORES_CANCELLED))
);
I see that you can use takeUntil but you need to explicitly fire a cancel action. I see in the docs that switchMap will take the latest and cancel all the others - do I have to implement a cancel interface in my api call? In this case it would be a firebase query to firestore.
From a comment I made in a GitHub issue:
Because they have a time dimension, there are multiple flattening strategies for observables:
With mergeMap (which has flatMap as an alias), received observables are subscribed to concurrently and their emitted values are flattened into the output stream.
With concatMap, received observables are queued and are subscribed to one after the other, as each completes. (concatMap is mergeMap with a concurrency of one.)
With switchMap, when an observable is received it's subscribed to and any subscription to a previously received observable is unsubscribed.
With exhaustMap, when an observable is received it's subscribed to unless there is a subscription to a previously received observable and that observable has not yet completed - in which case the received observable is ignored.
So, like Mark said in his answer, when switchMap receives a subsequent action, it will unsubscribe from any incomplete request.
However, the request won't be cancelled until the debounced action makes it to the switchMap. If you want to cancel any pending requests immediately upon another move - rather than wait for the debounce duration - you can use takeUntil with the FETCH_NEARBY_STORES action:
const fetchNearbyStoresEpic = action$ =>
action$.ofType(FETCH_NEARBY_STORES)
.debounceTime(500)
.switchMap(action =>
db.collection('stores')
.where('location', '<=', action.payload.max).
.where('location', '>=', action.payload.min)
.map(response => fetchNearbyStoresFulfilled(response))
.takeUntil(action$.ofType(FETCH_NEARBY_STORES))
);
That should effect the immediate unsubscription from a request upon another move. (Off the top of my head, I cannot recall the behaviour of action$ in redux-observable. It's possible that you might need to append a skip(1) to the observable passed to takeUntil. Try it and see.)
And, as Mark mentioned, this is predicated on the underlying implementation cancelling the request upon unsubscription.
switchMap will abandon its previous observable when a new emission is send through it. Depending on your underlying HTTP library and if it supports cancellation (Observable aware) this should suffice.
Because no implementation details have been provided in your question you will have to look into fetchNearbyStoresFulfilled to see if it uses an Observable aware http client. If it internally is using promises then no cancellation support is provided.
Related
I have a NestJS application, and need to send an HTTP request to another server, so I am using the HttpModule (#nestjs/axios).
I need the data from that request, but the returned type is <Observable<AxiosResponse<any,any>>, where I need just the AxiosResponse.
Reading over the RxJS documentation, it looks like the prescribed way to handle this situation is to make use of RxJS lastValueFrom() or firstValueFrom(), after the deprecation of toPromise().
However, there is a warning attached:
Only use lastValueFrom function if you know an Observable will eventually complete. The firstValueFrom function should be used if you know an Observable will emit at least one value or will eventually complete. If the source Observable does not complete or emit, you will end up with a Promise that is hung up, and potentially all of the state of an async function hanging out in memory. To avoid this situation, look into adding something like timeout, take, takeWhile, or takeUntil amongst others.
The solution that I came up with was:
const response = this.httpService.post('the-url').pipe(take(1))
const axiosResponse: AxiosResponse = await lastValueFrom(response)
TypeScript at least is not complaining. Is this a suitable way to get at the underlying Axios response?
A promise once triggered will only get resolved or rejected once. Once it's resolved the observable is completed. It is one of the major differences between promise and observable which has capability to emit multiple times like callback.
Therefore there is no need to add pipe(take(1)), just use lastValueFrom is sufficient
If you construct an observable from a promise you don't need lastValueFrom nor take. Once subscribed to, it emits once then completes immediately:
const {from} = rxjs;
const answer$ = from(Promise.resolve(42));
answer$.subscribe({
next(x) {
console.log(`answer=${x}`);
},
complete() {
console.log('done');
}
});
<script src="https://unpkg.com/rxjs#%5E7/dist/bundles/rxjs.umd.min.js"></script>
I want to make sure that all events, which were submitted before complete was invoked on the observable, are logged.
I'm aware that operators exist that stop emission of events (takeUntil, etc.) completely.
The question that I have is whether other operators exist which would lead to emissions not being sent if the complete on the subject is sent too 'early'. Are there cases where it would be beneficial to wait with the completion of the observable until the event was handled by the observer?
For example, are there situations (imagine any other RxJS operator instead of the delay) where the following code ...
const subj = new Subject<string>();
subj.pipe(delay(500))
.subscribe((val) => {
console.log(val);
subj.complete();
});
subj.next('1');
... makes more sense than that ...
const subj = new Subject<string>();
subj.pipe(delay(500))
.subscribe((val) => {
console.log(val);
});
subj.next('1');
subj.complete();
... when the subject should only emit one value?
Or is completing the subject immediately after next always safe in such situations?
If there are other factors I'm not aware of (e.g. synchronous vs. asynchronous execution of code) please mention them as well.
In general,
subj.next('1');
subj.complete();
is safe. As far as I know, none of the existing RxJS operators use a completion to cancel/unsubscribe observables early.
That being said, I can easily create such an operator myself. switchMap will cancel currently running inner observables when it receives it's next emission. You could, for example, create a custom operator that unsubscribes and exists early when it receives a complete event.
If your worried about that, however, you're out of luck. No matter what workaround you imagine, I can probably write an operator that will ruin your code. For example filter(_ => false) will stop the 1 from being emitted before the complete in either of the two cases you've described.
In the end, you and your coworkers must write good code (and test!) and RxJS doesn't change that.
In rxjs, if the complete method is invoked should I also invoke unsubscribe?
For example I have a timer from rxjs and I set to 5 seconds.
After the subscribe function is invoked should I run also unsubscribe from the timer?
import { timer } from "rxjs";
const token = timer(5 * 1000).subscribe({
next: () => {
console.log("xxx");
token.unsubscribe(); // <---- should I do it to to free memory?
},
complete: () => {
console.log("aaa");
}
});
The question:
if the complete method is invoked should I also invoke unsubscribe?
No. An observable can only complete or error once. A completed or errored observable is done. There's no need to unsubscribe.
Timer, as invoked above, will only emit once and then complete. Unless you want to cancel the timer, there's no need to unsubscribe. If you give the timer a second argument, then it changes to act more like interval. Then you must unsubscribe.
Generalized:
Any short-lived observable will complete on its own. This is the case with promises, HTTP calls, observables created with of or from(array). These generally don't need to be unsubscribed unless some business logic dictates otherwise.
Long-lived observables don't complete on their own and must be managed somehow. User interactions tend to have no defined end (Think DOM events like button clicks). intervals have no defined end as well.
How to unsubscribe
The best solutions will be those that use operators that handle subscription/unsubscription on your behalf. They require no extra cognitive load in the best circumstances and manage to contain/manage errors relatively well (less spooky action at a distance) in the more exotic circumstances.
Most higher-order operators do this (concat, merge, concatMap, switchMap, mergeMap, ect). Other operators like take, takeUntil, takeWhile, ect let you use a more declarative style to manage subscriptions.
Where possible, these are preferable as they're all less likely to cause strange errors or confusion within a team that is using them.
Of course you should unsubscribe, just check token in console log. It has props: closed: true, _subscriptions: null, isStopped: true.
Compare if not unsubscribe:
closed: false isStopped: false _subscriptions: Array[1].
If you want the complete value, you have to complete your Subject, in that case - timer.
I am just getting into RxJs and Observables in general. I grasped the idea that often you can create "self-contained" Observable by utilizing "takeUntil()".
In one online-course I am watching the teacher says "I did not unsubscribe from anything in 10 years because I always use takeUntil() to create ending streams of events". This is his example:
var getElementDrags = elmt => elmt
.mouseDowns.map(() => document.mouseMoves.takeUntil(document.mouseUps))
.concatAll();
That is very nice for the "inner" Observables. But the one outer Observable on "mousedown" never really gets unsubscribed from...
Do we still need to unsubscribe from those?
Is it still good practice to unsubscribe/dispose when the user leaves the page?
In example you have - you are not subscribing to anything... RxJS is lazy, and it will subscribe to mouseDowns only when you will subscribe to resulting observable, and of course - it will unsubscribe from underlining observables when you will unsubscribe from resulting observable.
But, generally - yes, it is a good practice to unsubscribe when you are subscribing to something… But - while using RxJS, typically you will not need to subscribe manually, and when you need - chances are that you need subscription while app is running(so no need to unsubscribe).
The only exceptions are - when you are developing own operators, or connecting to something outside…
For example if you have react component and use life-cycle hocks for subscription to updates on mount, and unsubscribe when un-mounting.
Here is my library for that purpose https://github.com/zxbodya/rx-react-container - it combines observables, subjects and react component into new observable with renderable items...
const app$ = createContainer(
App, // react component
{totalCount$}, // observables with data
{plusOne$, minusOne$} // observers for user actions
);
const appElement = document.getElementById('app');
const appSubscription = app$.forEach(renderApp=>render(renderApp(), appElement));
In result you have only one subscription to manage for a whole application(appSubscription), and no need to unsubscribe - since it is used while app is running.
The same thing, about routing and unsubscribe when navigating away - in simplified case you will have just flatMapLatest over observable with current location, that will return observable(like app$ above) for each location… And again you do not need to subscribe/unsubscribe manually - flatMapLatest will do it internally.
Below is code from the Ngrx example: https://github.com/ngrx/example-app/blob/master/src/effects/book.ts My question is why in the first #Effect, it uses switchMap while the others use mergeMap. Is that because the first #Effect is dealing with network, and with the switchMap you can cancel the previous network request if it's running?
#Effect() search$ = this.updates$
.whenAction(BookActions.SEARCH)
.map<string>(toPayload)
.filter(query => query !== '')
.switchMap(query => this.googleBooks.searchBooks(query)
.map(books => this.bookActions.searchComplete(books))
.catch(() => Observable.of(this.bookActions.searchComplete([])))
);
#Effect() clearSearch$ = this.updates$
.whenAction(BookActions.SEARCH)
.map<string>(toPayload)
.filter(query => query === '')
.mapTo(this.bookActions.searchComplete([]));
#Effect() addBookToCollection$ = this.updates$
.whenAction(BookActions.ADD_TO_COLLECTION)
.map<Book>(toPayload)
.mergeMap(book => this.db.insert('books', [ book ])
.mapTo(this.bookActions.addToCollectionSuccess(book))
.catch(() => Observable.of(
this.bookActions.addToCollectionFail(book)
))
);
#Effect() removeBookFromCollection$ = this.updates$
.whenAction(BookActions.REMOVE_FROM_COLLECTION)
.map<Book>(toPayload)
.mergeMap(book => this.db.executeWrite('books', 'delete', [ book.id ])
.mapTo(this.bookActions.removeFromCollectionSuccess(book))
.catch(() => Observable.of(
this.bookActions.removeFromCollectionFail(book)
))
);
}
You are correct; switchMap will unsubscribe from the Observable returned by its project argument as soon as it has invoked the project function again to produce a new Observable.
RxJs is incredibly powerful and dense, but its high level of abstraction can sometimes make code hard to understand. Let me debunk the marble diagrams and docs given by #Andy Hole a little and bring them up to date. You may find the marble syntax reference highly valuable to better understand rxjs operators from their tests (at least I found this missing/not highlighted enough in the official docs).
mergeMap
The first line in the diagram is the source Observable which emits (1,3,5) at different times. The second line in the diagram is the prototypical Observable returned by the project function i => ... passed to the .mergeMap() operator.
When the source Observable emits the item 1, mergeMap() invokes the project function with i=1. The returned Observable will emit 10 three times, every 10 frames (see marble syntax reference). The same happens when the source Observable emits item 3 and the project function creates an Observable that emits 30 three times. Note that the result of mergeMap() contains all three elements generated by each Observable returned from project.
switchMap
This is different with switchMap(), which will unsubscribe from the Observable returned by project as soon as it has invoked it again on a new element. The marble diagram indicates this with the missing third 30 item in the output Observable.
In the example you have given, this leads to the cancellation of the pending search request. This is a very nice but hard-to-get-right property, which you get for free by combining switchMap() with cancellable Observables returned by Angular's Http service. This can save you a lot of headaches without worrying about properly handling all the race conditions that typically occur with async cancellation.
You are right.
As you can see, switchMap is used with search functionality. The searchbox in this example is programmed to basically emit a search request when the user enters text in the textbox (with a 350ms debounce or delay).
This means that when the user enters 'har', ngrx sends a search request to the service. When the user enters another letter 'r', the previous request is canceled (since we are not interested in 'har' anymore, but 'harr').
It is very nicely shown in the marble diagrams provided in another answer.
In mergeMap, the previous Observables are not canceled and therefore '30' and '50' are mixed together. Using switchMap, only the 5s are emitted, because the 3's are canceled.
mergeMap
Projects each source value to an Observable which is merged in the output Observable.
Maps each value to an Observable, then flattens all of these inner Observables using mergeAll.
switchMap
Projects each source value to an Observable which is merged in the output Observable, emitting values only from the most recently projected Observable.
Maps each value to an Observable, then flattens all of these inner Observables using switch.
Source: ES6 Observables in RxJS
You don't want an API save data request to cancel. That is why you would use mergeMap. A search query can be thrown away, no loss of data, and the user might be editing their query and are not interested in the data for the old one. Hence switchMap.
Yes, if you are no longer concerned with the response of the previous request when a new Input arrives switchMap is a suitable operator than mergeMap.