use*_*472 1 rxjs redux-observable
背景:
我正在使用史诗来管理请求.
对于我发送令牌的每个请求,该令牌可能会过期,但可以在宽限期内刷新.
我正在为每个请求使用令牌,但在发送任何请求之前我想检查令牌是否已过期,如果过期且有宽限期,则首先刷新令牌然后继续相应的操作
所有请求都有自己的史诗.
现在我尝试的是所有操作的预挂钩,以检查令牌可能刷新它然后继续操作.
希望这能解释.
// 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()) ...
......
Run Code Online (Sandbox Code Playgroud)
但这种方法不适用于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))
)
);
Run Code Online (Sandbox Code Playgroud)
编辑:这是我最初的建议,但它比上面的复杂得多.它也有一些好处,但上面的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);
Run Code Online (Sandbox Code Playgroud)
如上所述,有很多方法可以解决这个问题.我可以创建一些辅助函数,你在史诗中使用需要令牌而不是这种"超级史诗"的方法.做一些看似不那么复杂的事情.
| 归档时间: |
|
| 查看次数: |
1130 次 |
| 最近记录: |