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 hasflatMap
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
ismergeMap
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.