React+RxJS轮询Epic:定时器结束触发指定Action的实现问询
Great question! Let's fix that boundary case where your polling stops after 1 minute but doesn't dispatch any action. Here are three approaches tailored to different needs:
Approach 1: Quick Fix with endWith
If you don't need to distinguish between polling ending naturally (after 12 cycles) vs being manually stopped via POLL_STOP, use the endWith operator. This dispatches your desired action whenever the stream completes—whether from hitting the 12-cycle limit or receiving a POLL_STOP action.
const processPollEpic = action$ => action$.pipe( ofType(POLL_START), mergeMap(() => timer(0, 5000).pipe( exhaustMap(() => from(service.getSmth()).pipe(map(resp => { if (resp.isSuccess) { return processPollStop(); } return processPollContinue(); }))), takeUntil(action$.ofType(POLL_STOP)), take(12), // Dispatch your timeout action when the stream completes endWith(processPollTimeout()), // Replace with your actual timeout action creator )), );
Approach 2: Distinguish Natural Timeout vs Manual Stop
If you only want the timeout action to fire when polling finishes all 12 cycles (not when manually stopped), we can track whether the stream was interrupted:
const processPollEpic = action$ => action$.pipe( ofType(POLL_START), mergeMap(() => { // Stream to track manual stop events const manualStop$ = action$.ofType(POLL_STOP); return timer(0, 5000).pipe( exhaustMap(() => from(service.getSmth()).pipe(map(resp => { if (resp.isSuccess) { return processPollStop(); } return processPollContinue(); }))), takeUntil(manualStop$), take(12), // Only append the timeout action if we didn't stop manually concatWith( of(processPollTimeout()).pipe(takeUntil(manualStop$)) ) ); }) );
Approach 3: Strict 1-Minute Timeout (Most Accurate)
Your original take(12) assumes 12 cycles over 55 seconds (since timer(0,5000) fires immediately). For a strict 1-minute window (regardless of cycle count), use a dedicated timer for the timeout:
const processPollEpic = action$ => action$.pipe( ofType(POLL_START), mergeMap(() => { // Dispatch timeout action exactly after 1 minute const timeoutAction$ = timer(60000).pipe(map(() => processPollTimeout())); return merge( // Regular polling stream timer(0, 5000).pipe( exhaustMap(() => from(service.getSmth()).pipe(map(resp => { if (resp.isSuccess) { return processPollStop(); } return processPollContinue(); }))), ), // Timeout action stream timeoutAction$ ).pipe( // Stop polling if we get POLL_STOP or the timeout action takeUntil( merge(action$.ofType(POLL_STOP), timeoutAction$) ) ); }) );
Key Notes:
endWithis ideal for simple cases where you just need an action on stream completion, no matter the reason.- The second approach ensures the timeout action only triggers when polling runs its full course without manual intervention.
- The third approach avoids timing discrepancies from network delays, guaranteeing polling stops exactly at the 1-minute mark.
内容的提问来源于stack exchange,提问作者Vadym Chornyi

