React Redux с Redux Observable дождитесь завершения действия аутентификации, прежде чем продолжить

Я занимаюсь разработкой .NET, точнее WPF. Когда вы хотите вызвать действие с аутентификацией в C #, это примерно то, что вам нужно сделать.

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

Довольно просто: вызовите действие, если вы не прошли аутентификацию, покажите диалоговое окно, в котором пользователь может ввести учетные данные и снова вызвать действие с учетными данными.

Сейчас я пытаюсь реализовать ту же функциональность в redux с epics.

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, которое изменит состояние в редукторе, чтобы открыть окно, пользователь сможет ввести свои учетные данные, и когда он это сделает, он вызовет действие Authenticate.done. Когда это будет сделано, epic продолжит обещание вызвать действие, которое вызовет другое действие, когда оно будет завершено. Я делаю что-то неправильно? Разве это не слишком сложно для такой простой вещи? Я не думаю, что это проблема с редукцией, а скорее с наблюдаемыми и эпическими редукциями.


person MistyK    schedule 18.11.2017    source источник


Ответы (1)


Это может соответствовать вашим потребностям. Важно отметить разбиение проблемы на несколько эпосов.

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.

person Robert Farley    schedule 20.11.2017
comment
Окей, звучит хорошо. Однако вы можете представить, что аутентификация будет выполняться не только для вытягивания, но и для выталкивания. Как бы вы расширили свое решение для работы с обоими? AuthDoneEpic должна иметь информацию о том, какое действие он должен вызвать и что делать, если оно не удалось. Вы бы сохранили его в полезной нагрузке действия Authenticate.done или где? - person MistyK; 20.11.2017
comment
Да, вам нужно будет включить аутентифицированное действие, которое будет выполняться следующим, в полезные данные authenticate.done. Я отредактировал свой ответ, чтобы показать один из способов сделать это. - person Robert Farley; 20.11.2017
comment
Означает ли это, что я должен хранить полезную нагрузку между запуском и завершением в редукторе? - person MistyK; 21.11.2017
comment
Вы должны передать его создателю действия authenticate.done. Возможно, вам удастся сохранить только pull или push, а затем выполнить поиск в authenticatedActions. - person Robert Farley; 21.11.2017