Is there any way to use throw
with an Error object inside ngrx-effects streams without completing the stream?
I've read these great answers on why the stream is being killed by throwing an error:
@ngrx Effect does not run the second time
https://github.com/ngrx/platform/issues/646
My question is if I'm implementing the Angular ErrorHandler
to catch errors, if I'm going to be able to use that with ngrx effects.
@Effect()
loginUserEffect: Observable<loginActions.Actions> = this.actions$
.ofType(loginActions.LOGIN_USER)
.map((action: loginActions.LoginUser) => action.payload)
.mergeMap(payload => {
return this.loginService
.authenticate(payload)
.map(response => new loginActions.LoginUserSuccess(response))
.catch((error: HttpErrorResponse) =>
of(new loginActions.LoginUserFailure(error))
)
})
@Effect({ dispatch: false })
loginUserEffectSuccess$ = this.actions$
.ofType(loginActions.LOGIN_USER_SUCCESS)
.do(() => this.router.navigate(['/account-home']))
@Effect({ dispatch: false })
loginUserEffectFailure$ = this.actions$
.ofType(loginActions.LOGIN_USER_FAILURE)
.map((action: loginActions.LoginUserFailure) => {
throw action.payload // Stream completes
})
I imagine I could create some way of dealing with errors that doesn't involve throwing anything, but wanted to make sure I needed to go that route or if there was a way to keep both of them peacefully coexisting.
Currently in my class that implements ErrorHander
, I have this:
@Injectable()
export class GlobalErrorHandler implements ErrorHandler {
private messagesService: MessagesService
private router: Router
constructor(
private injector: Injector, // DI workaround (https://stackoverflow.com/a/41585902)
private errorLoggerService: ErrorLoggerService
) {
// DI workaround (https://stackoverflow.com/a/41585902)
setTimeout(() => (this.messagesService = injector.get(MessagesService)))
setTimeout(() => (this.router = injector.get(Router)))
}
handleError(error) {
if (error instanceof HttpErrorResponse) {
this.handleServerError(error)
} else if (error instanceof ClientError) {
this.handleClientError(error)
} else {
this.handleUnexpectedError(error)
}
}
Which means I just throw errors and they are handled based on the type
If you want to throw an error that will not be caught by the observable and will instead be reported to a global error handler, you have throw the error from outside of the observable's call stack.
You could do something like this:
@Effect({ dispatch: false })
loginUserEffectFailure$ = this.actions$
.ofType(loginActions.LOGIN_USER_FAILURE)
.do(action => setTimeout(() => { throw action.payload; }, 0))
})
However, as your effects are in a class that already uses Angular's DI mechanism, I would suggest you instead inject your GlobalErrorHandler
and call it directly.
Without the setTimeout
calls I think it would be clearer and easier to test, too.
I ended up creating an Error action:
@Effect()
loginUserEffectFailure$: Observable<
errorsActions.Actions
> = this.actions$
.ofType(loginActions.LOGIN_USER_FAILURE)
.map(
(action: loginActions.LoginUserFailure) =>
new errorsActions.Error(action.payload)
)
and having that call the error handler in a dedicated effect:
@Effect({ dispatch: false })
errorEffect$ = this.actions$
.ofType(errorsActions.ERROR)
.map((action: errorsActions.Error) =>
this.clientErrorHandler.handleError(action.payload)
)
I left the global error handler just for unexpected exceptions to be caught and logged.
来源:https://stackoverflow.com/questions/47896171/is-it-possible-to-throw-errors-inside-ngrx-effects-without-completing-the-observ