Как справиться с обновлением токена с помощью наблюдаемого редукса? - PullRequest
0 голосов
/ 24 мая 2019

Я изучал 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(
    ...

Спасибо!

1 Ответ

0 голосов
/ 26 мая 2019

В оригинальной версии makeApiRequest создание собственного оператора выглядит немного странно.Во-первых, на самом деле не работает с чем-либо, излучаемым наблюдаемым источником.Во-вторых, он использует жестко запрограммированные параметры запроса.Похоже, вы определили и решили эти проблемы в отредактированной версии.

Если вы выдаваете ошибку и используете catchError или выпускаете два разных типа элементов и проверяете тип, то это действительно вопрос стиля кодирования.Они оба работают, и сродни разнице между этим:

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

... и этим:

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

Разница в том, что при маршруте try-catch выпросто не нужно проверять isRefreshAction.Если вы примените это к своим эпикам / операторам и выдадите ошибку, mapApiResponse просто преобразуется в простой оператор map.

Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...