React-Redux+RxJS:SendSMSEffect中setTimeout无法触发setDisabled问题
Got it, let's break down why your setTimeout isn't firing the setDisabled(false) action, and fix it properly.
The Core Issue
Your line of(setTimeout(() => setDisabled(false), 5000)) is the problem. When you wrap setTimeout in of(), RxJS immediately emits the timer ID returned by setTimeout as an Observable value—it doesn't wait for the 5-second delay to run your setDisabled action. Native browser timers don't play nicely with RxJS streams directly; you need to use RxJS's built-in operators for timing logic.
Fix 1: Replace setTimeout with RxJS timer
Use timer(5000) instead—it creates an Observable that emits a single value after 5 seconds. Then map that value to your setDisabled(false) action to ensure it runs only after the delay.
Fix 2: Fix Stream Completeness
Right now, if payload.smsType !== smsType.Validation, your switchMap doesn't return any Observable. This breaks the entire stream, so the subsequent hideLoader and delay logic never runs. We'll add an EMPTY Observable to handle that case, and a catchError to ensure failures don't stop the rest of the flow.
Corrected Code
import { timer, EMPTY } from 'rxjs'; // ... 其他必要导入 export const sendSMSEffect: Epic<IFluxPayloadAction<any, string>, any, IAppState> = (action$, state$) => action$.pipe( ofType(SMSActions.SendSMS), map((action) => action.payload), map((payload) => { const mobile:string = getMobilePhoneFromPersonalDetails(state$.value); return {mobile, smsType:payload}; }), switchMap((payload) => { const smsSelector = getSmsDetailsSelector(state$.value); const smsContent = retrieveSms(payload.smsType, smsSelector); return concat( // 可选:如果触发发送时还没禁用按钮,在这里添加 // of(setDisabled(true)), of(showLoader(sms.sendingSMS)), from(sendSMSService(smsContent, payload.mobile, true)).pipe( switchMap((response:ISMSResponse) => { const wasSMSSent = response.Sent === 'Y' ? true : false; if (payload.smsType === smsType.Validation) { return concat( of(wasValidationSMSSent(wasSMSSent)), of(updateSMSValidationRetry()) ); } // 处理非Validation类型的短信,返回空流保持完整 return EMPTY; }), // 捕获请求错误,避免流中断 catchError(() => EMPTY) ), of(hideLoader(sms.sendingSMS)), // 5秒后发出启用按钮的动作 timer(5000).pipe(map(() => setDisabled(false))) ); }) );
Extra Notes
- Make sure you import
timerandEMPTYfromrxjs—they're core operators for this logic. - If you haven't already, add
of(setDisabled(true))at the start of theconcatto disable the button immediately when the send action triggers. - The
catchErrorensures that even if the SMS request fails, the loader hides and the button re-enables after 5 seconds.
内容的提问来源于stack exchange,提问作者Fraser Steel

