Dispatch an action in response to cancellation

。_饼干妹妹 提交于 2019-12-22 08:06:33

问题


I started with the cancellation recipe from the redux-observable docs and want to extend it a bit.

Basically I have a scenario where after the cancellation is triggered, using takeUntil I want dispatch another action to cleanup, etc.

This is what I came up with so far: https://jsbin.com/zemenu/195/edit?js,output

Start a "Fetch User Info" and then hit Cancel. I want it to execute actions in this order:
- USER/FETCH
- REQUEST/STARTED
- USER/CANCELLED
- REQUEST/CANCELLED

This works in the way I have it setup right now. But, I have to rely on passing dispatch into the requestSequence function and then trigger it in finally. Is there a cleaner way to do this just with observable operators? So when that USER.CANCELLED is triggered some final action is mapped to inside the requestSequence observable.

Redux logger is enabled so check the console for all the actions.


回答1:


Instead of using .takeUntil(), it sounds like you want to use .race(), which is fairly aptly named. Whichever stream emits first, wins! The other is unsubscribed.

You'll need to restructure some things a bit to use it as you want. You want to isolate the first action you emit immediately, your request.onStart(meta), separate from the ajax request Observable.fromPromise(apiCall(...args)). Then you want to race directly between that ajax and the cancellation, so you'd need to pass in the action$ ActionsObservable since you have all this in a helper.

https://jsbin.com/suvaka/edit?js,output

function requestSequence(apiCall, args, meta, action$) {
  return Observable.of(request.onStart(meta))
    .concat(
      Observable.fromPromise(apiCall(...args))
        .map((payload) => request.onSuccess(payload, meta))
        .catch((e) => Observable.of(request.onError(e, meta)))
        .race(
          action$.ofType(USER.CANCELLED)
            .map(() => request.onCancel(meta))
        )
    );
}

const fetchUserEpic = (action$, store) =>
  action$.ofType(USER.FETCH)
    .mergeMap(action =>
      requestSequence(
        userRequest, 
        [`/api/users/${action.payload}`],
        { activity: USER.FETCH, path: 'user' },
        action$
      )   
    );

Side note: be careful about premature abstractions like making those sorts of helpers. Even though you may repeat things in some epics, I've found abstracting it can make it much harder to grok later, especially if it's someone else who didn't write the code and aren't an Rx guru. Only you can know whether this advice applies to you and your codebase, of course.

The primary confusing point for me is all the arguments you have to pass to requestSequence, which will be tough for many to understand when they first come across it. If you find that very very commonly your epics do exactly the same thing and you want to reuse, perhaps abstracting the entire epic would be more clear, and create API utilities like userRequest below that you can test independently.

(untested, basically pseudo code)

const createApiEpic = options =>
  action$ =>
    action$.ofType(options.on)
      .mergeMap(action =>
        Observable.of(request.onStart(meta))
          .concat(
            options.effect(action.payload)
              .map(payload => request.onSuccess(payload, meta))
              .catch(e => Observable.of(request.onError(e, meta)))
              .race(
                action$.ofType(options.cancel)
                  .map(() => request.onCancel(meta))
              )
          )
      );

const userRequest = id =>
  Observable.ajax.getJSON(`/api/users/${id}`);

const fetchUserEpic = createApiEpic({
  on: USER.FETCH,
  effect: userRequest
  cancel: USER.CANCELLED
});


来源:https://stackoverflow.com/questions/41032226/dispatch-an-action-in-response-to-cancellation

易学教程内所有资源均来自网络或用户发布的内容,如有违反法律规定的内容欢迎反馈
该文章没有解决你所遇到的问题?点击提问,说说你的问题,让更多的人一起探讨吧!