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

Redux Observable自动刷新令牌后无法重试主动作问题咨询

解决方案:修复自动刷新后的请求重试逻辑

你的核心问题在于错误使用了catchError的caught参数,以及操作符顺序错误,导致重试逻辑无法触发。以下是修复后的完整实现:

修复后的代码

1. 调整handleError函数(重新分发原动作)

import { Observable, of } from 'rxjs';
import { mergeMap, take, takeUntil } from 'rxjs/operators';
import { PayloadAction } from '@reduxjs/toolkit';

// 导入你的action和类型
import { 
  refreshActionCreator, 
  refreshSuccessActionCreator, 
  workSessionErrorActionCreator 
} from './auth-actions';
import { LOGOUT_ACTION, GET_ACTIVE_WORK_SESSION_ACTION } from './action-types';

const handleError = (
  action$: Observable<any>, 
  err: any, 
  originalAction: PayloadAction<string>
) => {
  if (err.response?.errors?.[0]?.extensions?.code === "ACCESS_DENIED") {
    // 先发送刷新令牌动作,等待刷新成功后重新分发原请求动作
    return of(refreshActionCreator()).pipe(
      mergeMap(() => action$.pipe(
        ofType(refreshSuccessActionCreator),
        takeUntil(action$.pipe(ofType(LOGOUT_ACTION))),
        take(1),
        map(() => originalAction) // 重新触发原请求动作
      ))
    );
  } else {
    return of(workSessionErrorActionCreator(err));
  }
};

2. 修改请求Epic(传递原动作到错误处理)

export const GetActiveWorkSessionEpic: Epic = (action$: Observable<PayloadAction<string>>) =>
  action$.pipe(
    ofType(GET_ACTIVE_WORK_SESSION_ACTION),
    mergeMap((originalAction) => {
      const userId = originalAction.payload;
      return RequestGetActiveWorkSession(userId).pipe(
        map(res => res.data?.workSession.getActiveWorkSessionByUserId),
        map(workSession => SetActiveWorkSession(workSession)),
        catchError((err) => handleError(action$, err, originalAction))
      );
    })
  );

3. 保留原RefreshEpic逻辑

你的RefreshEpic实现是正确的,无需修改:

export const RefreshEpic: Epic = (action$) =>
  action$.pipe(
    ofType(REFRESH_ACTION),
    mergeMap(() => RequestRefresh().pipe(
      map(res => {
        if (res.data.auth.refresh) {
          SetAccessToken(res.data.auth.refresh);
        }
        return refreshSuccessActionCreator();
      }),
      catchError(() => of(logoutActionCreator()))
    ))
  );

问题原因分析

1. mergeMap未生效的根本原因

你之前使用mergeMap(() => caught)时,caught是已经失败并完成的Observable(原请求出错后,这个Observable就结束了),订阅它不会产生任何值,因此重试逻辑完全没有触发。

正确的做法是重新触发原请求动作(或重新发起请求),而不是复用已完成的caught Observable。

2. 操作符顺序错误

你之前的mergeWith(of(refreshActionCreator()))是和监听refreshSuccess的逻辑并行执行的,导致刷新动作发送后,还没等刷新成功就尝试订阅已失效的caught,进一步导致重试失败。

修复后改为先发送刷新动作,再等待刷新成功,最后触发重试,保证了执行顺序:原请求→失败→刷新令牌→刷新成功→重试原请求。


你的疑问解答

疑问1:为何ofType必须传入actionCreator而非类型?传入类型时无限请求主动作

ofType同时支持传入动作类型字符串和actionCreator,两者本质都是匹配动作的type字段。你遇到的无限循环问题,核心不是ofType的参数类型,而是:

  • 之前错误使用caught导致重试请求再次失败,反复触发catchError→刷新→重试→失败的循环;
  • 当传入类型字符串时,可能因为逻辑漏洞(比如未限制重试次数)导致循环无法终止,而传入actionCreator时可能因偶然因素暂时没暴露,但根本问题是重试逻辑错误。

疑问2:为何此处的mergeMap()没有生效?

如前所述,mergeMap中的caught是已完成的Observable,订阅后不会发出任何数据,因此mergeMap内的逻辑完全不会执行。修复后改为重新分发原动作,让Epic重新处理请求,就能正常触发重试。


额外优化:防止无限循环

如果刷新后的令牌仍然无效(比如refresh token过期),会再次触发ACCESS_DENIED,导致无限循环。可以在重试逻辑中添加错误捕获,避免这种情况:

// 在handleError的重试逻辑中添加二次错误处理
mergeMap(() => RequestGetActiveWorkSession(userId).pipe(
  map(res => res.data?.workSession.getActiveWorkSessionByUserId),
  map(workSession => SetActiveWorkSession(workSession)),
  catchError(retryErr => {
    // 重试失败直接返回错误,不再触发刷新
    return of(workSessionErrorActionCreator(retryErr));
  })
))

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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.07.16 04:35:03