How to not trigger 'complete' handler when error occurred in concatMap? - rxjs

When dealing with concatMap, how can I abort the execution of observables further down the line and prevent calling the completion handler?
Here is a simple example.
of(...[1, 2, 3]).pipe(
concatMap(t => of(t)),
map(n => {
console.log(n);
if (n === 2) {
throw new Error('OK, fail here');
}
}),
catchError((e, c) => of(console.log('Caught ' + e)))
)
.subscribe(
{
complete: () => console.log('Complete should not be triggered in an error case, but here it is'),
error: (err: any) => {
console.log('I did never trigger, thats ok!');
}
});
A source observable emits 1,2,3 which is piped into concatMap.
There are two possible scenarios: All three observables are emitted without error in this case complete handler should be triggered. Or the other case depicted here: Somewhere down the line there is an error like when n === 2. ConcatMap stops executing the next observable which is perfect but it still triggers the completion handler, which is undesired.
Actual result
1
2
Caught Error: OK, fail here
Complete should not be triggered in an error case, but here it is
Desired result
1
2
Caught Error: OK, fail here
Any hints? My previous attempt was to throw in the next handler but that turned out to be really bad :/ as it triggered a hostReportError

Ok,
since I was able to answer all the other fun questions regarding RxJS by myself, here we go :)
Remember: catchError replaces the faulted observable by some other observable. If catchError would therefore return an observable via throwError it would halt the execution and even better, it would trigger the error handler that was idle before.
of(...[1, 2, 3]).pipe(
concatMap(t => of(t)),
map(n => {
console.log(n);
if (n === 2) {
throw new Error('OK, fail here');
}
}),
catchError((e, c) => throwError(e)) // This line has changed
)
.subscribe(
{
complete: () => console.log('Complete should not be triggered in an error case, but here it is'),
error: (err: any) => {
console.log('Now I have something to do, yay!');
}
});

Related

RxJS- Conditionally using RetryWhen operator

I would like to devise a scalable error handling strategy for my Observable-based Firebase requests. To do so, I am trying to create a custom RxJS operator for each type of error (e.g. unauthenticated, internal, you name it). The operators would be stacked after the request as follows:
FirebaseRequestObservable.pipe(
...
handleUnauthenticatedError(),
handleInternalError(),
handleYouNameItError(),
...
)
This works fine if my error handling operators only constitute of a catchError operator, in which case its internal structure can simply be:
source.pipe(
catchError((err) => {
if (err !== errorThisOperatorShouldHandle) {
throw err
}
handleError
}
))
So that the error trickles down to the next custom operator if it shouldn't be handled by that operator.
The problem comes in when my error handling logic for a certain error involves using retryWhen. I do not know how I could then have the operator conditionally employ retryWhen if it is the right error, and re-throw the error to the next operator if it isn't.
They're pretty similar.
Here's how I might implement these two:
Handle a specific error.
function handleYouNameItError<T>(): MonoTypeOperatorFunction<T> {
return catchError(err => {
if(err !== youNameItError){
return throwError(() => err);
}else{
// handleError
return EMPTY;
}
});
}
Retry a specific error.
function retryYouNameItAnotherError<T>(): MonoTypeOperatorFunction<T> {
return retryWhen(err$ => err$.pipe(
tap(err => {
if(err !== youNameItAnotherError){
throw err;
}
}),
// Delay 1s between retries
delay(1000),
// Only retry 5 times
take(5),
// After 5 retries, throw a new error
concatWith(throwError(() =>
new YouNameItAnotherErrorFailedAfterRetries()
))
));
}
Use them as operators :)
FirebaseRequestObservable.pipe(
...
handleYouNameItError(),
retryYouNameItAnotherError()
...
);

RxJs throwError does not trigger catch in Promise

I've got an Api call that is converted to a promise. My handleError function inside the observable re-throws via throwError. This re-thrown error does not trigger any catch in the outer Promise chain.
callApi() {
return this.http.get(`${this.baseUrl}/someapi`)
.pipe(
map((data: any) => this.extractData(data)),
catchError(error => this.handleError(error))
).toPromise();
handleError(error) {
console.error(error);
return throwError(error || 'Server error');
}
Calling code...
this.someService.callApi()
.then((response) => {
// THIS GETS CALLED AFTER throwError
// do something cool with response
this.someVar = response;
})
.catch((error) => {
// WE NEVER GET TO HERE, even when I force my api to throw an error
console.log(`Custom error message here. error = ${error.message}`);
this.displayErrorGettingToken();
});
Why doesn't the throwError trigger the Promise catch?
You should not use toPromise() when possible.
Use subscribe instead of then.
Also when you catch the error in a pipe it won't be thrown in then because you already caught it, also when you throw the error in a catch error, it won't be emitted into the regular pipe flow of your response.
callApi() {
return this.http.get(`${this.baseUrl}/someapi`);
}
This is totally ok. Http.get() returns a singleton observable stream, which emits only ONE value and then completes. Subscribe to the Observable.
this.someService.callApi()
.subscribe((response) => {
// THIS GETS CALLED always wenn everything is ok
this.someVar = response;
},
(error:HttpErrorResponse) =>{
console.log(`Custom error message here. error ${error.message}`);
this.displayErrorGettingToken();
});
Observable is like an extended version of promise. Use it.

Rxjs: "throw" statement not being handled by piped catchError

A "authenticationService" provides the following authenticate method. I'm unable to enter the piped catchError. What am I missing?
authenticate(credentials: { username: string; password: string }){
return new Observable<any>((observer: Observer<any>) => {
// ... calling the service, obtaining a promise
const authenticationPromise = ... ;
// This is a promise, it must be converted to an Observable
authenticationPromise
.then(() => {
observer.next('ok');
observer.complete();
})
.catch(err => {
console.log('service error ' + err);
throw new Error('crap');
});
});
}
Setting all the ngrx & ngrx/effect part aside, this authentication method is called upon user request:
(redux stuff).(({ payload }: actions.LoginRequestAction) =>
context.authService.authenticate(payload)
.pipe(
map(() => new GenericSuccessAction(...))
// Even though a throw statement happened in the authenticate method, this is never reached:
catchError((err: Error) => {
console.log('[debug] error caught: ', err);
return of(new actions.LoginFailureAction());
}),
)
)
As stated here, catchError is used to:
Gracefully handle errors in an observable sequence.
First of all, you are basically handling the error in your promise by catching the error in your promise. Throwing the error in the promise doesn't return an observable that emits an error.
You can:
Convert your promise into an observable and don't use .catch at all.
Return the error as an observable with rxjs' throwError(err)
Either way, the way you create your observable is questionable.
This is a much better and concise way to handle promises in rxjs:
from(authenticationPromise)
.pipe(
map(() => 'ok')
)

Observables: Complete vs finally vs done

When speaking about Observables (especially rxjs), what is the difference between "finally" and "done" or "complete"?
Finally always happens whenever an observable sequence terminates (including errors); completed only happens when it terminates without errors.
Finally:
Invokes a specified action after the source observable sequence
terminates gracefully or exceptionally.
https://github.com/Reactive-Extensions/RxJS/blob/master/doc/api/core/operators/finally.md
OnCompleted:
An Observable calls this method after it has called onNext for the
final time, if it has not encountered any errors.
http://reactivex.io/documentation/observable.html
"Done" isn't an rx/observables concept. I've just seen it printed in examples of "Complete" / "OnComplete".
Note: when you call subscribe, the syntax is usually:
observable.subscribe([observer] | [onNext], [onError], [onCompleted]);
// Like this:
observable.subscribe(
(value) => { ... },
(error) => { ... },
() => { console.log('complete!'); }
);
or
observable.subscribe({
next: x => console.log('got value ' + x),
error: err => console.error('something wrong occurred: ' + err),
complete: () => console.log('done'),
});
Whereas finally is handled like this:
observable.finally(() => { console.log('finally!'); })
.subscribe(...) // you can still call subscribe
To be more precise, the finally() operator adds a dispose handler. The complete notification just calls the complete handler in observers.
What this means in practise:
When using finally() the callback is going to be called in every situation that causes unsubscription. That's when complete and error notifications are received by observers but also when you manually unsubscribe.
See demo: https://jsbin.com/dasexol/edit?js,console
complete or error handlers are called only when the appropriate notification is received. Only 0 - 1 handlers can be called but never both of them.

rxjs switchMap onerror behavior

I have a confusion about switchMap in rxjs Observable:
for example i have next code:
Observable.fromEvent(button, 'click')
.switchMap(() => Observable.fromPromise(fetch('http://return-error.com')))
.subscribe(
(response) => {
console.log(response);
},
(error) => {
console.log(error);
}
);
If I get error from fetch, subscription is interrupted. So is there way to handle it to not create new subscription for any error?
I have even tried to catch error and return Observable, but subscription is interrupted anyway.
upd: how to deal with angular 2 http.get instead of fetch?
It's always more helpful if you make a Bin when asking these questions. I did it for you on this one.
You simply need to swallow the error in the fetch call. E.g.
fetch('bad-url').catch(err => 'Error but keep going')
Here's the demo. Click the document (output) to fire the event.
http://jsbin.com/vavugakere/edit?js,console,output
(You'll need a browser with native Fetch implementation or it'll throw an error)

Resources