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

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')
)

Related

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

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!');
}
});

Unit testing NestJS Observable Http Retry

I'm making a request to a 3rd party API via NestJS's built in HttpService. I'm trying to simulate a scenario where the initial call to one of this api's endpoints might return an empty array on the first try. I'd like to use RxJS's retryWhen to hit the api again after a delay of 1 second. I'm currently unable to get the unit test to mock the second response however:
it('Retries view account status if needed', (done) => {
jest.spyOn(httpService, 'post')
.mockReturnValueOnce(of(failView)) // mock gets stuck on returning this value
.mockReturnValueOnce(of(successfulView));
const accountId = '0812081208';
const batchNo = '39cba402-bfa9-424c-b265-1c98204df7ea';
const response =client.viewAccountStatus(accountId, batchNo);
response.subscribe(
data => {
expect(data[0].accountNo)
.toBe('0812081208');
expect(data[0].companyName)
.toBe('Some company name');
done();
},
)
});
My implementation is:
viewAccountStatus(accountId: string, batchNo: string): Observable<any> {
const verificationRequest = new VerificationRequest();
verificationRequest.accountNo = accountId;
verificationRequest.batchNo = batchNo;
this.logger.debug(`Calling 3rd party service with batchNo: ${batchNo}`);
const config = {
headers: {
'Content-Type': 'application/json',
},
};
const response = this.httpService.post(url, verificationRequest, config)
.pipe(
map(res => {
console.log(res.data); // always empty
if (res.status >= 400) {
throw new HttpException(res.statusText, res.status);
}
if (!res.data.length) {
this.logger.debug('Response was empty');
throw new HttpException('Account not found', 404);
}
return res.data;
}),
retryWhen(errors => {
this.logger.debug(`Retrying accountId: ${accountId}`);
// It's entirely possible the first call will return an empty array
// So we retry with a backoff
return errors.pipe(
delayWhen(() => timer(1000)),
take(1),
);
}),
);
return response;
}
When logging from inside the initial map, I can see that the array is always empty. It's as if the second mocked value never happens. Perhaps I also have a solid misunderstanding of how observables work and I should somehow be trying to assert against the SECOND value that gets emitted? Regardless, when the observable retries, we should be seeing that second mocked value, right?
I'm also getting
: Timeout - Async callback was not invoked within the 5000ms timeout specified by jest.setTimeout.Timeout - Async callback was not invoked within the 5000ms timeout specified by jest.setTimeout.Error:
On each run... so I'm guessing I'm not calling done() in the right place.
I think the problem is that retryWhen(notifier) will resubscribe to the same source when its notifier emits.
Meaning that if you have
new Observable(s => {
s.next(1);
s.next(2);
s.error(new Error('err!'));
}).pipe(
retryWhen(/* ... */)
)
The callback will be invoked every time the source is re-subscribed. In your example, it will call the logic which is responsible for sending the request, but it won't call the post method again.
The source could be thought of as the Observable's callback: s => { ... }.
What I think you'll have to do is to conditionally choose the source, based on whether the error took place or not.
Maybe you could use mockImplementation:
let hasErr = false;
jest.spyOn(httpService, 'post')
.mockImplementation(
() => hasErr ? of(successView) : (hasErr = true, of(failView))
)
Edit
I think the above does not do anything different, where's what I think mockImplementation should look like:
let err = false;
mockImplementation(
() => new Observable(s => {
if (err) {
s.next(success)
}
else {
err = true;
s.next(fail)
}
})
)

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.

What is the difference between catch and catchError in RxJS. And How to handle the network error returned by API call?

mergeMap( (action)=>{
const data = action.data;
console.log(state$.value,'\n',action.data);
Calling API here. How to handle the network error returned by this call?
from(axios.post('http://localhost:3000/addContactIntoDirectory',
{directoryId: state$.value.reducer1.SelectedDirectory, contact: data.contact})
))
basically, in RXJS catch and catchError is identical. You can refer the documentation RxJs catch/catchError for more info. Docs also states that we have to return observable from catchError.
have a look at the given example related to your library axios context,
axios.post('/formulas/create', { name: "Atul", parts: "Mishra" })
.then(response => {
console.log(response)
}).catch(error => {
console.log(error.response)
});

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