I refactored my http layer to go from a promised-based implementation to observables using rxjs. The problem that I am facing is that the code crashes whenever server response is 400 or 500,
Axios.request(config).pipe(map(((response: AxiosResponse) => response), catchError(e => {
return new Observable(e);
})));
The problem I am facing is that the error is not being handled by the catchError callback. I am looking for a way in which the error is handled by the catchError callback so that the response can be handled gracefully.
I don't know your problem is this but you can do this:
import { EMPTY } from 'rxjs';
Axios.request(config).pipe(map(((response: AxiosResponse) => response),
catchError(() => EMPTY)));
EMPTY is an object that is imported from rxjs libaray.
It looks like you want to turn the error notification to next notification so you need to use return of(e) eventually EMPTY if you want to suppress the error.
new Observable(e) is probably what throws the error in your case because the parameter passed to Observable needs to be a function which e is not in this case.
Related
This is more like question than resolve a problem.
I would like to know if there any scenario that both "Success" and Error" is not triggered.
The post call to "/logout" will result Http status return code 200
with empty respond body which is expected
import { httpClient } from angular/common/http;
private http: HttpClient;
this.http.post<any>('/logout', {})
.subscribe(
() => {
console.log("Logout");
}, error => {
console.log(error);
},
() => {
console.log("Finally");
});
It will output "Finally" 100% of time. That means success and error is not triggered at all.
Is there possibilities that either success and error not trigger. And clearly the http status code response is 200 OK.
Update:
The answer that #meriton provided work great.
Observable, in general, are not required to complete or error. They may remain live, and continue to emit values, forever.
However, Observable returned by HttpClient are guaranteed to terminate with either success or error (though the error may take a few minutes in case of a timeout) according to the HTTP status of the response. The presence of absence of a body does not affect this. If the request is successful, the observable will emit exactly one value: the response body (or null if the response body is absent).
I can not reproduce your claim that "success or error is not triggered at all". May you have misunderstood what the callbacks mean? When you provide three callbacks to subscribe, they are, in order:
the next callback, which receives emitted values
the error callback, which notifies that the Observable has aborted due to an error
the complete callback, which notifies that the Observable has completed successfully
The danger of mixing up callbacks is one reason why the RXJS team has deprecated passing several callbacks as separate arguments to subscribe in RXJS 8. The future proof way to write your code would be:
this.http.post<any>('/logout', {}).subscribe({
complete: () => {
console.log("Logout successful")
},
error: (error) => {
console.log(error);
}
});
BTW, none of these callbacks mean "finally", as in the finally clause of a try-statement, which is executed both in case of success and error. If you want to do something irrespective of whether the Observable completed successfully or failed with an error, you could use the finalize operator.
http library success depends on Status:200, it does not require message.body to be present
Example code of using RXJS pipe flow, where you can control the flow by capturing success & error, controlling timeout. It also demonstrates how you can use .subscribe() method as classic Finally
Example RXJS Flow:
this.http
.post<any>('/logout',{})
.pipe(
map(() => { // OK
return { success: true, err: null };
}),
timeout(10000), // CONTROL TIMEOUT
catchError((e) => { // IN CASE OF ERROR
return of({success: false, err:e});
})
)
.subscribe((result) => { // FINALLY here
if (result.success) {
console.log('Logged out successfully');
} else {
console.log('Logout failed', result.err);
}
});
I am using Axios in my React-Native app to communicate with a Nodejs backend, and am using react-redux dispatch to call the actions that will utilize Axios. However, no matter what I try I land up getting "Unhandled Promise Rejection" anytime there is an error of any sort. This is frustrating me so much I'm about to give up on Axios and just go back to using fetch. I've googled this problem (which seems to be very common indeed), but have not found any solid solution yet.
Here is an example of how I'm using Axios in my actions to send requests to my Nodejs backend server:
export const getAppointments = (userId) => {
return async (dispatch) => {
const request = axios
.get(`${SERVER_BOOKINGS}/upcoming_appointments/${userId}`)
.then((response) => {
let ourPayload = {};
ourPayload.success = response.data.success;
if (ourPayload.success) {
ourPayload.bookings = response.data.bookings;
dispatch({
type: GET_UPCOMING_APPOINTMENTS,
payload: ourPayload,
});
}
})
.catch((err) => {
console.log("caught an error, HERE:", err);
//throw err
});
};
};
And here is the code I'm using to call this action:
const getAppointments = async () => {
try {
await dispatch(
bookingActions.getAppointments(userObject.userData.userId)
);
} catch (error) {
console.log("we actually caught an axios error client side!");
}
}
If I leave the code exactly as above and I deliberately cause an error response from my Nodejs code , I get a console.log message saying "caught an error, HERE", but get nothing from the catch block where I actually dispatch the action (2nd block of code).
If I uncomment out the throw err line in the first catch block, I still get the console.log, still get nothing from the second catch block.... but now I get an Unhandled Promise Rejection warning.
Right now, this code works very well as long as there isn't an error, and is completely worthless anytime there is an one. In all honestly, I don't know whether the issue is one with axios, react-redux dispatch or just a failure on my part to understand the way Promises are meant to work, but I've wasted untold hours trying to figure out what should be a really simple matter of catching errors... and I'm falling behind schedule on this project because of it. Any advice/help would be greatly appreciated!
I think that the problem is caused since "bookingActions.getAppointments" is not a Promise function so there is no need to "try-catch" it.
try to change it like this:
const getAppointments = () => dispatch(bookingActions.getAppointments(userObject.userData.userId));
I have an observable that submits the form submit$. This observable may end up with error with status code 403 what means that a user is not authorised and has to log in first.
Is there a way where I could on specific error code invoke another observable which performs an authorisation process. When authorisation is succeeded I want to repeat the submit$ without having user to invoke that observable once again.
To illustrate steps I want to have:
A user tries to submit and submit$ is being subscribed
This ends up with error with status code 403
Observable calls another authorise$ observable which has own workflow
When authorise$ succeeds the submit$ is invoked again
The process completes with success or error
If There is an error during authorise$ abort the submit$ process
I tried an approach here where I am separating into two observables, submit$ and authenticationSubmit$, and then I merge them again. I haven't tested it, and I am writing http.post(...) twice, so it is not exactly as you described it.
import { merge } from 'rxjs';
import { filter, switchMap } from 'rxjs/operators';
...
const submit$ = http.post(...);
const authenticationAndSubmit$ = submit$.pipe(
filter(httpResponse => httpResponse.status === 403),
switchMap(() => authService.login()),
filter(authResult => authResult === 'success'),
switchMap(() => http.post(...))
);
merge(submit$, authenticationAndSubmit$)
.pipe(
filter(httpResponse => httpResponse.status === 200),
)
.subscribe(httpResponse => {
// Do something
});
Hi i am using angular 5 and i am writing a global handler for the same which looks like following.
#Injectable()
export class ErrorsHandler implements ErrorHandler {
constructor(
private injector: Injector,
) { }
handleError(error: Error | HttpErrorResponse) {
const router = this.injector.get(Router);
const zone = this.injector.get(NgZone);
console.log('Here')
console.log(error)
if (error instanceof HttpErrorResponse) {
// Client Error Happend
zone.run(() => router.navigate(['/error'], { queryParams: { error: JSON.stringify(error) } }))
} else {
// Log the error anyway
router.navigate(['/error'], { queryParams: { error: JSON.stringify({ message: 'Failed' }) } });
}
}
}
Everything works fine in Observable world ie if i do a failed http call like following
fireServerError() {
this.httpService
.get('https://jsonplaceholder.typicode.com/1')
.subscribe(data => console.log('Data: ', data));
}
and if the server call fails i get an error object properly as shown in the console image
But instead of that if i change it to a promise using toPromise(), like following
fireServerError() {
this.httpService
.get('https://jsonplaceholder.typicode.com/1')
.toPromise();
}
i get the following string stack trace instead of error Object itself
What am i doing wrong. How to throw/get the error object in case of unhandled promise rejections. Please help. I am stuck;
Please find the stackblitz link Here
Once you use .toPromise(), you are essentially putting the behavior into a different execution context (read ECMAScript 10.4), which means that errors must be handled in/around the new execution context rather than having them bubble up in Angular like you are expecting.
I can't say I entirely follow your example code (is fireServerError always supposed to throw an error via an HTTP call?), but it seems like you want to try to execute promises without local error handling, but rather to have any error from a promise bubble up to the angular error handling. I'm not sure I'd recommend that, I believe it's best-practice to handle promise errors locally (i.e. using Promise.prototype.catch or a try/await/catch block when you create the promise).
That being said, error handling is of course a complicated topic and if you are dead set on just handling all errors at the global level then you can try using a global window event handler to catch all unhandled promise rejections and handle them there:
window.addEventListener("unhandledrejection", event => {
event.preventDefault(); // prevent the default promise rejection behavior
console.error(event); // do whatever you want with the error
}, false);
The MDN guide on promises may also help clear some things up, hope that helps!
I'm trying to find a general way to handle errors on promise chains. In the following snipped I'd like to handle an potential connection-error directly in my connection.js.
connection.js
function getData() {
return fetch(myEndpoint)
.catch(err => handleConnectionError(err)) // redirect to error page or similar
}
app.js
// import connection
connection.getData()
.then(data => handleData(data.foo))
So there are two ways this scenario could play out:
If I catch the error in connection.js without throwing a new one, it will continue the promise chain and handleData() will fail
If I throw an Error again after handling it, the Promise chain wont be executed any further, but then I have an unhandled-promise-rejection error in the console.
So is there actually no better way, than catching and handling the errors everytime I'm using the getData() function somewhere in my app?
The best way to go about this would be to have a final catch to take care of all errors. E.G:
function errorHandler(e) {
if (e instanceof x) {
//handle here
}
}
And then:
fetch(endPoint).then(doSomething).then(doAnotherThing).catch(err => errorHandler(err))
If fetch or any other promise along the chain produces an error, the rest of the then() statements will be skipped and will be catched by the final catch function. From there you will need to make sure that all types of errors that could be thrown by all promises are taken care of.
However, you will need to get rid of the catch in getData as it will cause the rest of the chain to run as usual since the error is taken care of.
Hope that helps
So is there actually no better way, than catching and handling the errors everytime I'm using the getData() function somewhere in my app?
Yes, that's precisely what you should do:
function getData() {
return fetch(myEndpoint)
}
// elsewhere:
connection.getData().then(handleData, handleConnectionError);
You usually don't know whether you always want to "redirect to error page or similar". Maybe at some places in your app, you can handle connection errors by faking response data, and in other places you might want to show an error message instead of triggering a redirect.