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

