如何使用Redux Observable处理令牌刷新?

时间:2019-05-24 17:40:35

标签: reactjs typescript rxjs redux-observable

我一直在学习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;
};

但是我发现了另一个问题-我无法使用传递的动作来格式化有效载荷(用于id等)。还有另一种方法来获取该动作作为参数,以便我可以将回调传递给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(
    ...

谢谢!

1 个答案:

答案 0 :(得分:0)

makeApiRequest的原始版本中,创建自定义运算符有点奇怪。首先,它实际上不能对可观察到的源发出的任何东西进行操作。其次,它使用硬编码的请求参数。您似乎已经在编辑后的版本中找到并解决了这些问题。

是否发出错误并使用catchError还是发出两种不同类型的项目并检查类型确实是有关编码样式的问题。它们都起作用,这类似于两者之间的区别:

function fetchCurrentEmployee() {
  const result = makeApiRequest()
  if (isRefreshAction(result)) {
    ...
  } else {
    ...
  }
}

...还有这个

function fetchCurrentEmployee() {
  try {
    const resp = makeApiRequest()
    ...
  } catch (error) {
    ...
  }
}

区别在于,使用try-catch路由,您根本不需要isRefreshAction检查。如果将其应用于史诗/运算符并发出错误,则mapApiResponse可以简化为普通的map运算符。