使用takeUntil时因先前派发的action导致Effect请求被立即取消的问题
问题描述
当用户选择订单时,订单UI组件会派发selectOrder action:
selectOrder(orderId): void { this.store$.dispatch( selectOrder({orderId}) ); }
随后会加载所有相关数据:
private getOrderOnSelectOrder(): void { this.store$.select(selectSelectedOrder).pipe(filter((selectedOrder: Order | null): selectedOrder is Order => Boolean(selectedOrder))) .subscribe((selectedOrder: Order) => { this.store$.dispatch(getOrder({ orderId: selectedOrder.id })) } ); }
一个Effect监听getOrder action以向服务器发起请求:
public getOrder$ = createEffect(() => this.actions$.pipe( ofType(getOrder), switchMap((props: GetOrderProps) => this.orderService.getOrder(props.orderId).pipe( map((order: Order) => /* Logic for successfully received order */ ), catchError((error: HttpErrorResponse) => /* Logic for failure */ ), takeUntil(this.actions$.pipe(ofType(selectOrder))), ), ), ), );
添加takeUntil是为了在用户选择新订单时丢弃旧请求,但所有请求都被立即取消,无法获取订单数据。想知道该行为的原因——明明请求是在selectOrder action之后发起的,为何对应的Observable会受到影响?
问题原因与解决方案
核心原因
takeUntil的触发逻辑是:一旦监听的Observable(这里是actions$.pipe(ofType(selectOrder)))发出值,当前数据流就会立即终止。
你的流程里,selectOrder action派发后会同步触发getOrder action的派发,但Redux的action流是同步的——当Effect里的takeUntil开始监听时,它会“捕获”到刚刚派发的那同一个selectOrder action,直接触发终止逻辑,导致请求刚发起就被取消。
解决方案
有两种可行的处理方式:
- 跳过当前触发请求的
selectOrder
让takeUntil只监听当前请求发起之后的新selectOrderaction,在ofType(selectOrder)后添加skip(1):
public getOrder$ = createEffect(() => this.actions$.pipe( ofType(getOrder), switchMap((props: GetOrderProps) => this.orderService.getOrder(props.orderId).pipe( map((order: Order) => /* Logic for successfully received order */ ), catchError((error: HttpErrorResponse) => /* Logic for failure */ ), takeUntil(this.actions$.pipe( ofType(selectOrder), skip(1) // 跳过触发当前请求的那个selectOrder )), ), ), ), );
- 直接利用
switchMap的特性switchMap本身就具备“新请求进来时自动取消未完成的旧请求”的能力,你的需求其实已经被switchMap覆盖了,完全可以去掉takeUntil这一行:
public getOrder$ = createEffect(() => this.actions$.pipe( ofType(getOrder), switchMap((props: GetOrderProps) => this.orderService.getOrder(props.orderId).pipe( map((order: Order) => /* Logic for successfully received order */ ), catchError((error: HttpErrorResponse) => /* Logic for failure */ ) ), ), ), );
内容的提问来源于stack exchange,提问作者Ilya Loskutov
相关产品推荐
相关产品推荐

