如何避免@ngrx Effects中SwitchMap错误中断及后续请求执行
你的问题核心有两个:一是认证注册失败后仍会执行后续的创建账户、登录请求,二是在Effect末尾加catch会导致后续操作无法触发。下面分步骤解决:
1. 终止失败后的后续请求流程
原来的代码中,authenticationService.SignUp的catch操作符把错误转换成了发射失败action的成功Observable,这会让后续的switchMap误以为流程正常,继续执行(甚至把失败action当成参数传给accountService.CreateAccount),完全不符合预期。
正确的思路是:在请求失败时,直接返回失败action,不进入后续的请求链。我们可以用mergeMap拆分成功/失败分支,只有成功时才继续执行后续请求:
@Effect() AccountSigningUp$ = this.actions$ .ofType(ACCOUNT_SIGNING_UP) .map((action: AccountActions.AccountSigningUp) => action.payload) .switchMap(signUpModel => // 第一步:执行认证提供商注册 this.authenticationService.SignUp(signUpModel) // 注册成功时,才进入创建账户流程 .mergeMap(signUpSuccessResult => { // 第二步:在API内创建账户 return this.accountService.CreateAccount(signUpSuccessResult as SignUpModel) // 创建账户成功时,才进入登录流程 .mergeMap(createAccountSuccessResult => { // 第三步:执行登录 return this.authenticationService.Login(createAccountSuccessResult as LoginModel) .map(tokenModel => { this.tokenService.Save(tokenModel); return new AccountLoggedIn(); }) // 登录失败时,返回登录失败action,终止当前链 .catch(loginErr => { const loginErrModel = new LoginModel(); loginErrModel.message = loginErr; return Observable.of(new AccountLoginFailed(loginErrModel)); }); }) // 创建账户失败时,返回注册失败action,终止当前链 .catch(createAccountErr => { const signUpErrModel = new SignUpModel(); signUpErrModel.message = createAccountErr; return Observable.of(new AccountSignUpFailed(signUpErrModel)); }); }) // 认证注册失败时,直接返回失败action,完全终止后续所有流程 .catch(signUpErr => { const signUpErrModel = new SignUpModel(); signUpErrModel.message = signUpErr; return Observable.of(new AccountSignUpFailed(signUpErrModel)); }) // 注意:原代码末尾的.map(() => new AccountCreated())是逻辑错误! // 它会覆盖前面所有的action(包括登录成功/失败、注册失败等),请根据业务需求调整 );
2. 避免Effect停止监听的问题
你提到在Effect末尾加catch会导致第二次触发失效,原因是:当Observable抛出错误时,外层的catch会返回一个新的Observable,但当这个Observable执行完成后,整个Effect的监听流就会终止(Effect需要持续监听action,不能提前完成)。
解决这个问题的关键是:不要把catch放在Effect的最外层,而是将catch嵌入每个独立的请求内部,这样每个请求的错误只会终止当前的请求分支,不会影响整个Effect对后续ACCOUNT_SIGNING_UP action的监听。
额外的逻辑修正
原代码末尾的.map(() => new AccountCreated())会把前面所有的action(包括AccountLoggedIn、AccountSignUpFailed等)都强制转换成AccountCreated,这显然不符合业务逻辑。如果需要在登录成功后同时发送AccountLoggedIn和AccountCreated,可以调整为:
.map(tokenModel => { this.tokenService.Save(tokenModel); // 同时发送两个action return Observable.concat( Observable.of(new AccountLoggedIn()), Observable.of(new AccountCreated()) ); })
或者根据你的实际业务需求,调整action的发送时机。
内容的提问来源于stack exchange,提问作者WPalombini

