React Redux with Redux Observable 在继续之前等待身份验证操作完成

React Redux with Redux Observable wait for authentication action to finish before continuing

我的背景是 .NET 开发,准确地说是 WPF。当您想在 C# 中调用经过身份验证的操作时,这就是您大致必须要做的。

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

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

现在我正在尝试使用 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 中的状态以打开 window,用户将能够输入他的凭据,当他输入时,它将调用 authenticate.done 操作。完成后,epic 将继续承诺调用一个动作,该动作将在完成时引发另一个动作。难道我做错了什么?这么简单的事情岂不是太复杂了?我不认为这是 redux 的问题,而是 redux observable 和 epics.

这可能符合您的需要。需要注意的重要一点是将问题分解为多个 epics.

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 操作。