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.