Я изучал rxjs и собрал работающий поток обновления токена, но я хотел бы внести некоторые улучшения и застрял.
export const makeApiRequest = <
T extends (p: StandardAdminParams) => Promise<Request.ApiResult<any>>
>(
req: T,
params: RemainingApiCallArgs<T>,
action$: ActionsObservable<any>,
state$: StateObservable<RootState>
) =>
mergeMap(() =>
defer(() =>
req({
...params,
settings,
token: getToken(state$.value)!
})
).pipe(
mergeMap(async v => {
const resp = await v;
if (!resp.ok) {
// TODO find out if there's a better way here.
throw resp.error;
}
return resp as ApiReturnType<T>;
}),
catchError((_, source) => {
// TODO make this actually check if token is refreshing, etc.
return action$.pipe(
filter(isActionOf(authActions.refreshToken.success)),
takeUntil(action$.ofType(AuthActionConsts.REFRESH_TOKEN_FAILED)),
take(1),
mergeMap(() => source),
merge(of(authActions.refreshToken.request()))
);
})
)
);
Моя библиотека API не выдает исключений, она возвращает ok: boolean
. Однако во всех примерах учебных пособий по обновлению токенов используется оператор catchError()
, позволяющий повторить вызов при успешном обновлении. Мне было интересно, есть ли способ достичь того же, не прибегая к «фальшивой» ошибке здесь.
Вот пример использования:
const fetchCurrentEmployeeEpic: Epic<ActionTypes, any, RootState> = (
action$,
state$
) =>
action$.pipe(
filter(isActionOf(actions.requestCurrentEmployee.request)),
makeApiRequest(Employees.getSingle, {}, action$, state$),
mapApiResponse(v => {
if (v.ok) {
return actions.requestCurrentEmployee.success({
employee: v.value.data
});
}
return actions.requestCurrentEmployee.failure({
error: v.error
});
})
);
и mapApiResponse
обрабатывают оба выхода, либо действие обновления, либо тип возврата вызова API. Открыты для предложений о лучшем способе сделать это:
type RefreshTokenAction = ReturnType<typeof authActions.refreshToken.request>;
// Remove refresh token action type from union because our map function is only going to ever receive API responses.
type ExpectedMapParams<
T extends Request.ApiResult<any> | RefreshTokenAction
> = T extends RefreshTokenAction ? never : T;
/** Pass through refresh token action, otherwise pass api result to our callback */
export const mapApiResponse = <
T extends Request.ApiResult<any> | RefreshTokenAction,
R extends Action
>(
cb: (params: ExpectedMapParams<T>) => R
) =>
map((v: T) => {
if (isRefreshAction(v)) {
return v;
}
return cb(v as ExpectedMapParams<T>);
});
const isRefreshAction = <T extends Request.ApiResult<any> | RefreshTokenAction>(
v: T
): v is ExpectedMapParams<T> => {
if (isAction(v)) {
return true;
}
return false;
};
Однако я обнаружил другую проблему - я не могу использовать переданное действие для форматирования полезной нагрузки (для идентификаторов и т. Д.). Есть ли другой способ получить действие в качестве параметра, чтобы я мог передать обратный вызов в params т.е.
export const makeApiRequest = <
T extends (p: StandardAdminParams) => Promise<Request.ApiResult<any>>
>(
req: T,
params: (action: GETACTIONTYPEHERE) => RemainingApiCallArgs<T>,
action$: ActionsObservable<any>,
state$: StateObservable<RootState>
) =>
РЕДАКТИРОВАТЬ: мне удалось выяснить подпись полезной нагрузки действия, выполнив следующие действия:
export const makeApiRequest = <
T extends (p: any) => Promise<Request.ApiResult<any>>,
K extends Action
>(
req: T,
params: RemainingApiCallArgs<T> | ((p: K) => RemainingApiCallArgs<T>),
action$: ActionsObservable<any>,
state$: StateObservable<RootState>
) =>
mergeMap((action: K) =>
defer(() => {
const formattedParams =
typeof params === "function" ? params(action) : params;
return req({
...formattedParams,
settings,
token: getToken(state$.value)!
});
}).pipe(
...
Спасибо!