You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

如何用Redux-Observable处理Refresh Token?AccessDenied后重发请求问题

问题描述

我正在开发基于React、Redux和Redux-Observable的客户端应用,希望在初始请求收到AccessDenied异常时,自动发起Refresh Token请求并重发原请求。

我折腾了半天毫无头绪,只找到一篇旧指南但没法正常工作(可能是我操作错了)。我不需要现成的解决方案,只想要方向指引。

现有代码

Epics 实现

export const getDataEpic: Epic<LogoutAction> = (action$, _, { api }) =>
  action$.pipe(
    ofType(GET_DATA),
    exhaustMap(() => api.Request(getDataRequest).pipe(
      map((data) => {
        ...handle data
      }),
    )),
  );

export const refreshEpic: Epic<RefreshAction> = (action$, _, { api }) =>
  action$.pipe(
    ofType(REFRESH),
    exhaustMap(() => api.Request<RefreshResponse>(refreshRequest).pipe(
      mergeMap(({ data }) => {
        const { success, user, accessToken } = data.auth.refresh;

        const actions: Action[] = [success ? refreshSuccess() : refreshFailure()];
        if (!success)
          return from(actions);

        actions.push(setUser(user));

        api.SetAccessToken(accessToken);

        return from(actions);
      }),
    )),
  );

全局Epic错误处理器

export const RootEpic: Epic = (action$, store$, dependencies: EpicDependencies) =>
  combineEpics<any, any, any, any>(
    getDataEpic,
    refreshEpic,
    ...
  )(action$, store$, dependencies).pipe(
    catchError((error, source$) => {
      const api = dependencies.api;

      if (api.IsAccessDeniedError(error)) {
        return source$.pipe(
          ofType(REFRESH_SUCCESS),
          takeUntil(action$.pipe(ofType(REFRESH_FAILURE))),
          take(1),
          mergeMap(() => {
            return source$;
          }),
          mergeWith(of(refresh())),
        );
      }

      console.error(error);
      return source$;
    }),
  );

当前问题表现

数据请求收到accessDenied时会发送refresh请求,但不会重新派发getData动作。我试过多种方法,甚至怀疑方案可行性,还试过请求前检查令牌过期、用Redux中间件的方式。


方向指引
  • 将错误捕获从全局移到单个请求Epic内部:当前全局catchError捕获错误后,原请求的上下文(比如GET_DATA动作)已经流失,刷新成功后无法触发重发。把错误处理放到每个需要令牌的请求Epic里,就能保留原动作的信息。
  • 保留原请求动作用于重发:在单个请求Epic的catchError中,捕获到AccessDenied时,要把当前触发请求的动作(比如GET_DATA)存下来,等REFRESH_SUCCESS动作触发后,重新派发这个原动作。
  • 防止重复刷新请求:当多个请求同时触发AccessDenied时,要确保同一时间只发起一次刷新。可以用exhaustMap包裹刷新逻辑,或者在Redux状态中维护isRefreshing状态,后续请求进入等待队列,刷新完成后批量重发。
  • 利用状态管理刷新流程:在Redux状态中记录刷新状态(是否正在刷新、等待重发的请求队列),请求Epic先检查状态,若正在刷新则将原请求加入队列,刷新成功后遍历队列重发所有请求。
  • 考虑请求库拦截器配合:如果使用Axios这类HTTP库,可以在请求/响应拦截器中处理令牌刷新,但要注意和Redux-Observable的状态同步,避免出现状态不一致的问题。

内容的提问来源于stack exchange,提问作者Davyd Melnychenko

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.06.26 21:22:29