Debuting and cancellation by abbreviation

I'm trying to create a simple reduction-observable epic that subtracts and reverses. My code:

export const apiValidate = action$ => {
    return action$.ofType(validateRequestAction)
        .debounceTime(250)
        .switchMap((action) => (
            Observable.ajax({
                url: url,
                method: 'GET',
                crossDomain: true,
                headers: {
                    "Content-Type": 'application/json'
                },
                responseType: 'json'
            })
           .map(payload => (new APISuccessValidate()))
           .takeUntil(action$.ofType(validateCancelAction))
           .catch(payload => ([new APIFailureValidate()]))
    ));
};

      

The code only works sometimes. Depending on the speed of response from the server, I believe 1 out of 2 scenarios could happen.

Scenario 1 (works):

Time 0ms   - Fire validateRequestAction
Time 250ms - Ajax request occurs
Time 251ms - Fire validateCancelAction
Time 501ms - validateCancelAction passes debounce and cancels properly
Nothing else occurs

      

Scenario 2 (broken)

Time 0ms   - Fire validateRequestAction
Time 250ms - Ajax request occurs
Time 251ms - Fire validateCancelAction
Time 400ms - Ajax returns, APISuccessValidate action fired
Time 501ms - validateCancelAction passes debounce and there is nothing to cancel

      

Is it possible to write my epic so that only validateCancelAction can bypass debounceTime and cancel the ajax call without waiting?

Thank!

+3


source to share


1 answer


Actually you are only canceling your match validateRequestAction

, but yours .takeUntil(action$.ofType(validateCancelAction))

has no debouncing. I may be wrong, but if it is possible for the cancel action to be dispatched before the action has completed it, then the action it is intended to be canceled will not be canceled since the ajax request has not started yet, and takeUntil

. This race can be avoided if you don't undo the cancellation until your side effect (ajax in this case) starts and takeUntil

listens for a possible cancellation.

In your UI, you will not give the user the option to undo until some state has been set in the redux. Since our epic has to say redux when to flip it, we will need to release an action that we will listen to in reducers.

The easiest way is to use the operator startWith

:

export const apiValidate = action$ => {
    return action$.ofType(validateRequestAction)
        .debounceTime(250)
        .switchMap((action) => (
            Observable.ajax({
                url: url,
                method: 'GET',
                crossDomain: true,
                headers: {
                    "Content-Type": 'application/json'
                },
                responseType: 'json'
            })
          .map(payload => (new APISuccessValidate()))
          .takeUntil(action$.ofType(validateCancelAction))
          .catch(payload => ([new APIFailureValidate()]))
          .startWith({ type: 'validateRequestActionStarted' }) // <-- here
    ));
};

      

So, in this example, some reducer will listen validateRequestActionStarted

and change some state that the UI will then know that we should give them the option to cancel.




A completely different way to prevent this race from going entirely takeUntil

to the top-level thread and then simply "restarting" the epic with help repeat

if canceled. So that would close everything when we cancel; any pending ajaxs and any pending debounces.

export const apiValidate = action$ => {
    return action$.ofType(validateRequestAction)
        .debounceTime(250)
        .switchMap((action) => (
            Observable.ajax({
                url: url,
                method: 'GET',
                crossDomain: true,
                headers: {
                    "Content-Type": 'application/json'
                },
                responseType: 'json'
            })
          .map(payload => (new APISuccessValidate()))
          .catch(payload => ([new APIFailureValidate()]))
        ))
        .takeUntil(action$.ofType(validateCancelAction))
        .repeat();
};

      


It's worth noting that I used the terms epic and restart to help conceptualize our particular domain, but this is mostly just normal RxJS, so it's usually applicable outside of the reduction-observable. "Epic" is just a word for our function template that takes an action stream (input) and returns an action stream (output).

+7


source







All Articles