React Redux与Redux Observable等待身份验证操作完成后再继续

时间:2017-11-18 20:47:54

标签: reactjs rxjs redux-observable

我的背景是.NET开发,WPF正是如此。当你想在C#中调用经过身份验证的操作时,这就是你必须要做的事情。

var res = action();
if(res == Not authenticated)
{
  var cred = await showDialog();
  action(cred);
}

非常简单,如果您未经过身份验证,则可以调用操作显示对话框窗口,用户可以在其中输入凭据并再次使用凭据调用操作。

现在我正在尝试使用epics在redux中实现相同的功能。

export const pullCurrentBranchEpic = (action$: ActionsObservable<Action>, store: Store<ApplicationState>) =>
  action$
    .filter(actions.pullCurrentBranch.started.match)
    .mergeMap(action => Observable.merge(
      Observable.of(repoActions.authenticate.started({})),
      waitForActions(action$, repoActions.authenticate.done)
                  .mergeMap(async _=>{
                    const repository = await getCurrentRepository(store.getState());
                    await pullCurrentBranch(repository);
                  })
                  .map(_=>actions.pullCurrentBranch.done({params:{},result:{}}))
                  .race(
                    action$.ofType(repoActions.authenticate.failed.type)
                      .map(() => actions.pullCurrentBranch.failed({error:"User not authenticated",params:{}}))
                      .take(1))
    ));
export const waitForActions = (action$, ...reduxActions) => {
  const actionTypes = reduxActions.map(x => x.type);
  const obs = actionTypes.map(type => action$.ofType(type).take(1));
  return Observable.forkJoin(obs);
};

基本上epic将处理一个动作(我甚至不检查用户是否应该被认证,因为它会使这个代码翻倍)并且动作将运行另一个动作 - authenticate.started将改变reducer中的状态以打开在窗口中,用户将能够输入他的凭据,当他这样做时,它将调用authenticate.done操作。当它完成后,史诗将继续承诺调用一个动作,该动作将在完成时引发另一个动作。难道我做错了什么?对于这么简单的事情,这不是太复杂吗?我认为这不是redux的问题,而是redux可观察和史诗。

1 个答案:

答案 0 :(得分:2)

这可能符合您的需求。需要注意的重要事项是将问题分解为多个史诗。

const startAuthEpic = action$ => action$
  .filter(action.pullCurrentBranch.start.match)
  .map(action => repoActions.authenticate.started({});

const authDoneEpic = (action$, store) => action$
  .ofType(repoActions.authenticate.done.type)
  .mergeMap(() => {
    const repo = getCurrentRepository(store.getState());
    return Observable.fromPromise(pullCurrentBranch(repo))
      .map(() => actions.pullCurrentBranch.done({ ... }))
      .catch(error => Observable.of(actions.pullCurrentBranch.failed({ ... })))
  });

有多种方法可以包含更多经过身份验证的操作,其中一种方法是:

// each new authenticated action would need to fulfill the below contract
const authenticatedActions = [
  {
    action: 'pull',
    match: action.pullCurrentBranch.start.match,
    call: store => {
      const repo = getCurrentRepository(store.getState());
      return pullCurrentBranch(repo);
    },
    onOk: () => actions.pullCurrentBranch.done({}),
    onError: () => actions.pullCurrentBranch.failed({})
  },
  {
    action: 'push',
    match: action.pushCurrentBranch.start.match,
    call: store => {
      const repo = getCurrentRepository(store.getState());
      return pushCurrentBranch(repo);
    },
    onOk: () => actions.pushCurrentBranch.done({}),
    onError: () => actions.pushCurrentBranch.failed({})
  }
];

const startAuthEpic = action$ => action$
  .map(action => ({ action, matched: authenticatedActions.find(a => a.match(action)) }))
  .filter(wrapped => wrapped.matched)
  .map(wrapped => repoActions.authenticate.started({ whenAuthenticated: wrapped.matched }));

const authDoneEpic = (action$, store) => action$
  .ofType(repoActions.authenticated.done.type)
  .pluck('whenAuthenticated')
  .mergeMap(action => {
    const { call, onOk, onError } = action;
    return Observable.fromPromise(call(store))
      .map(onOk)
      .catch(err => Observable.of(onError(err)))
  });

您只需将whenAuthenticated属性传递给authenticate.done操作。