背景:
我正在使用史诗来管理请求。
对于每个请求,我发送一个令牌,该令牌可能会过期,但是可以在宽限期内刷新。
我正在为每个请求使用令牌,但是在发送任何请求之前,我想检查令牌是否已过期,以及是否已过期且具有宽限期,然后首先刷新令牌,然后继续执行相应的操作
所有的请求都有自己的史诗。
现在,我尝试的是对所有操作进行预检查,以检查令牌是否可能刷新它,然后继续进行操作。
希望这能解释。
// epics for querying data
// these epics are using a token, that is stored in redux state.
const getMenuEpic = action$ => ....
const getFoodListEpic = action$ => ....
const getFoodItemEpic = action$ => ....
...
// helper function to check
// if token has expired
const hasTokenExpired = (token) => .....
// refresh token
// this returns a new token in the promise
const refreshToken = fetch('http://.../refresh-toekn')
// i am trying to make an epic, that will fire
// before any actions in the application
// stop the action (let's call it action A)
// get token from redux state,
// verify if is valid or not
// if invalid call refresh token (async process),
// and when refresh token finished, proceed with the incoming action A
// if the token was valid then continue with action A.
const refreshEpic = (action$, store) =>
action$.map(() => store.getState().token)
.filter(Boolean)
.filter(token => hasTokenExpired(token))
.mergeMap(() => refreshToken()) ...
......
但是这种方法不适用于refreshEpic
不可能真正阻止某个动作到达还原器-实际上在传递给您的史诗之前就已经通过了-而是可以分派一个动作来表示要获取的意图,但实际上并不是触发它的原因。例如,UI调度FETCH_SOMETHING,而史诗般地看到它,确认有一个有效的刷新令牌(或获取一个新的刷新令牌),然后发出另一个操作以实际触发获取,例如FETCH_SOMETHING_WITH_TOKEN。
在这种情况下,尽管您可能会遇到许多具有相同要求的史诗,但这样做可能会很乏味。有很多方法可以使此操作变得容易。这是一对:
您可以编写一个帮您检查的助手,如果需要刷新,它将请求并等待,然后再继续。我将亲自在单独的专用史诗中处理实际的刷新,以防止出现多个并发刷新请求以及诸如此类的事情。
const requireValidToken = (action$, store, callback) => {
const needsRefresh = hasTokenExpired(store.getState().token);
if (needsRefresh) {
return action$.ofType(REFRESH_TOKEN_SUCCESS)
.take(1)
.takeUntil(action$.ofType(REFRESH_TOKEN_FAILED))
.mergeMap(() => callback(store.getState().token))
.startWith({ type: REFRESH_TOKEN });
} else {
return callback(store.getState().token);
}
};
const getMenuEpic = (action$, store) =>
action$.ofType(GET_MENU)
.switchMap(() =>
requireValidToken(action$, store, token =>
actuallyGetMenu(token)
.map(response => getMenuSuccess(response))
)
);
编辑:这是我最初的建议,但比上面的建议要复杂得多。它也有一些好处,但是IMO上面的一项将更易于使用和维护。
相反,您可以创建一个“超级史诗”,一个史诗本身由其他史诗组成并委托给其他史诗。根史诗是超史诗的一个例子。(我现在刚刚编造了这个词...大声笑)
我们可能要做的一件事是区分任何随机动作和需要auth令牌的动作-您不想检查auth令牌并为曾经调度的每个动作刷新它。一种简单的方法是在操作中包含一些元数据,例如{ meta: { requiresAuth: true } }
这要复杂得多,但是在其他解决方案上也有优势。这是我在说的一个粗略想法,但是它未经测试,可能不是100%考虑的。考虑它的灵感,而不是复制面食。
// action creator helper to add the requiresAuth metadata
const requiresAuth = action => ({
...action,
meta: {
...action.meta,
requiresAuth: true
}
});
// action creators
const getMenu = id => requiresAuth({
type: 'GET_MENU',
id
});
const getFoodList = () => requiresAuth({
type: 'GET_FOOD_LIST'
});
// epics
const getMenuEpic = action$ => stuff
const getFoodListEpic = action$ => stuff
const refreshTokenEpic = action$ =>
action$.ofType(REFRESH_TOKEN)
// If there's already a pending refreshToken() we'll ignore the new
// request to do it again since its redundant. If you instead want to
// cancel the pending one and start again, use switchMap()
.exhaustMap(() =>
Observable.from(refreshToken())
.map(response => ({
type: REFRESH_TOKEN_SUCCESS,
token: response.token
}))
// probably should force user to re-login or whatevs
.catch(error => Observable.of({
type: REFRESH_TOKEN_FAILED,
error
}))
);
// factory to create a "super-epic" which will only
// pass along requiresAuth actions when we have a
// valid token, refreshing it if needed before.
const createRequiresTokenEpic = (...epics) => (action$, ...rest) => {
// The epics we're delegating for
const delegatorEpic = combineEpics(...epics);
// We need some way to emit REFRESH_TOKEN actions
// so I just hacked it with a Subject. There is
// prolly a more elegant way to do this but #YOLO
const output$ = new Subject();
// This becomes action$ for all the epics we're delegating
// for. This will hold off on giving an action to those
// epics until we have a valid token. But remember,
// this doesn't delay your *reducers* from seeing it
// as its already been through them!
const filteredAction$ = action$
.mergeMap(action => {
if (action.meta && action.meta.requiresAuth) {
const needsRefresh = hasTokenExpired(store.getState().token);
if (needsRefresh) {
// Kick off the refreshing of the token
output$.next({ type: REFRESH_TOKEN });
// Wait for a successful refresh
return action$.ofType(REFRESH_TOKEN_SUCCESS)
.take(1)
.mapTo(action)
.takeUntil(action$.ofType(REFRESH_TOKEN_FAILED));
// Its wise to handle the case when refreshing fails.
// This example just gives up and never sends the
// original action through because presumably
// this is a fatal app state and should be handled
// in refreshTokenEpic (.e.g. force relogin)
}
}
// Actions which don't require auth are passed through as-is
return Observable.of(action);
});
return Observable.merge(
delegatorEpic(filteredAction$, ...rest),
output$
);
};
const requiresTokenEpic = createRequiresTokenEpic(getMenuEpic, getFoodList, ...etc);
如前所述,有许多方法可以解决此问题。我可以设想创建一些需要令牌的史诗内部使用的辅助函数,而不是这种“超级史诗”方法。做对您来说似乎不太复杂的事情。
本文收集自互联网,转载请注明来源。
如有侵权,请联系 [email protected] 删除。
我来说两句