Cancel previous requests and only fire the latest

2020-02-25 23:54发布

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.

2条回答
不美不萌又怎样
2楼-- · 2020-02-26 00:22

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.

查看更多
欢心
3楼-- · 2020-02-26 00:23

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.

查看更多
登录 后发表回答