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
操作。
我的背景是 .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
操作。