I recently started using Angular 2 and quickly discovered that when utilizing the HTTP client that a return code of 404 from the server ends up rejecting the promise.
Here's the code I have in my service:
export class CalculatorService {
private _serviceUrl = 'http://localhost:3000';
private headers = new Headers({'Content-Type': 'application/json'});
constructor(private http: Http) { }
getCode(request: CalculatorRequest) {
return this.http.post(this._serviceUrl, JSON.stringify(request), {headers: this.headers})
.toPromise()
.then(response => response.json() as CalculatorResponse)
.catch(this.handleError);
}
private handleError(error): Promise<any> {
const errMsg = (error.message) ? error.message :
error.status ? `${error.status} - ${error.statusText}` : 'Server Error : Service Unavailable';
if (errMsg != null) {
return Promise.reject(errMsg);
}
}
}
Unfortunately, instead of resolving the promise, my handleError method is called as the 404 causes a rejection.
Is there a straightforward way to resolve a request with a return code of 404?
Thanks!
Related
I am not able to access the response of error - 500 in axios
export const dowloadFilePDF = (data) => {
return axios
.request({
method: 'GET',
url: `${basePath + data[0]}`,
responseType: 'blob',
headers: { Authorization: Authorization },
})
.then(response => {
console.log(response)
let fileName = response.headers['content-disposition']?.split(';')[1]?.split('=')[1]?.split('"').join('')
fileName = fileName ? fileName : 'data.pdf'
fileDownload(response.data, fileName)
})
.catch((error) => {
console.log(error.response.data)
})
}
I am not getting the response instead its returning as
data : Blob {size: 215, type: 'application/json'}
According to the documentation, you can't assume error.response will be filled in. Here's the code the documentation shows with the inline comments explaining it:
Handling Errors
axios.get('/user/12345')
.catch(function (error) {
if (error.response) {
// The request was made and the server responded with a status code
// that falls out of the range of 2xx
console.log(error.response.data);
console.log(error.response.status);
console.log(error.response.headers);
} else if (error.request) {
// The request was made but no response was received
// `error.request` is an instance of XMLHttpRequest in the browser and an instance of
// http.ClientRequest in node.js
console.log(error.request);
} else {
// Something happened in setting up the request that triggered an Error
console.log('Error', error.message);
}
console.log(error.config);
});
There's another aspect to this as well: You're calling catch on the promise returned by then, not on the promise returned by axios. If the axios promise is rejected, you'll reach that rejection handler, but you'll also reach it if the axios promise is fulfilled but then your fulfillment handler throws an error (or returns a promise it ultimately rejects). In that latter case, the error probably won't have a response property at all.
the best way to catch errors instead of trying a lot of lines of code in the catch method by promise is using the tools in the Axios names interceptor.
interceptor has two property request and response. In response we can simulate the errors status and based on the status code we can do whatever we want. for example :
axios.interceptors.response.use(null, error => {
console.log("error : " , error);
const expectedError = error.response && error.response.status >= 400 &&
error.response.status < 500;
if (expectedError) {
return Promise.reject(error);
}
alert("unexpected error is happen");
});
if you need more help here is the original link
If I get a 401 error in my project, I want to make another API call and return the response. But I came to know that catchError() will only take throwError(some error) as a return statement, I want to send a response instead of throwing an error.
intercept(request: HttpRequest<any>, next: HttpHandler): Observable<HttpEvent<any>> {
let token: string = this.CookieService.get('token');
if ((token != null) && (token !== "")) {
let AuthRequest = this.addToken(request);
return next.handle(AuthRequest).pipe(
catchError(error => {
console.log(error);
if (error.status === 401 && error.message == 'token expired') {
// return throwError(error);
return this.handle401Error(request, next);
}
else {
return throwError(error);
}
}))
}
else return next.handle(request).pipe(
// finalize(()=>this.loaderService.hide())
);;
}
addToken(request: HttpRequest<any>)
{
//adding headers and returning them
var request2: HttpRequest<any>;
let token = this.CookieService.get('authToken');
const headers = new HttpHeaders().append('Authorization', token).append('Content-Type',
'application/json');
request2 = request.clone({ headers: headers });
return request2;
}
handle401Error(request, next)
{
//make another API to get a new token
// updating the token
return next.handle(AuthRequest); //re-hit the API with new token
}
}
I have checked the network tab, API is getting called and I am getting the expected response, but the problem is to bring that response in the component file. Right now I am getting facing this error in the response.
ERROR TypeError: You provided 'undefined' where a stream was expected. You can provide an Observable, Promise, Array, or Iterable.
at subscribeTo (subscribeTo.js:41)
at subscribeToResult (subscribeToResult.js:11)
at CatchSubscriber.push../node_modules/rxjs/_esm5/internal/operators/catchError.js.CatchSubscriber.error (catchError.js:43)
at XMLHttpRequest.onLoad (http.js:1707)
at ZoneDelegate.push../node_modules/zone.js/dist/zone.js.ZoneDelegate.invokeTask (zone.js:423)
at Object.onInvokeTask (core.js:26247)
at ZoneDelegate.push../node_modules/zone.js/dist/zone.js.ZoneDelegate.invokeTask (zone.js:422)
at Zone.push../node_modules/zone.js/dist/zone.js.Zone.runTask (zone.js:195)
at ZoneTask.push../node_modules/zone.js/dist/zone.js.ZoneTask.invokeTask [as invoke] (zone.js:498)
at invokeTask (zone.js:1693)
Is there any way to send the expected response than throwing an error using catchError?
You can use retryWhen operator for retries, it catchs the error and you decide what to do inside.
For example:
return next.handle(req).pipe(
// Will continue the pipe if a request attempt has succeeded without errors, or exceeded max retries.
retryWhen((errors: Observable<any>) => errors.pipe(
// Enable only 2 retries, +1 for the original request.
take(this.maxRetries + 1),
mergeMap((err: HttpErrorResponse, i: number) => {
if (i >= this.maxRetries) {
return throwError(err);
}
// retry
return of(err);
}),
))
)
I am implementing JWT refresh token in my angular project. I am following the below guide for that.
https://angular-academy.com/angular-jwt/
Here is my code:
intercept(req: HttpRequest<any>, next: HttpHandler): Observable<HttpEvent<any>> {
const user: any = this.storage.user;
const addToken = !req.urlWithParams.includes('token');
const token = user ? user.token : null;
if (token && !req.url.includes('token=') && addToken) {
req = this.addToken(req, user.token);
}
return next.handle(req).pipe(switchMap((event) => {
if (event instanceof HttpResponse && event.body.code === 401 && token) {
return this.handle401Error(req, next);
}
return next.handle(req);
}));
}
private addToken(request: HttpRequest<any>, token: string) {
return request.clone({
setHeaders: {
Authorization: `Bearer ${token}`,
},
setParams: {
token
}
});
}
private handle401Error(request: HttpRequest<any>, next: HttpHandler) {
if (!this.isRefreshing) {
this.isRefreshing = true;
this.refreshTokenSubject.next(null);
return this.getRefreshedJWT().pipe(
switchMap((res: any) => {
this.isRefreshing = false;
this.refreshTokenSubject.next(res.token);
return next.handle(this.addToken(request, res.token));
}));
} else {
return this.refreshTokenSubject.pipe(
filter(token => token != null),
take(1),
switchMap(jwt => {
return next.handle(this.addToken(request, jwt));
}));
}
}
getRefreshedJWT() {
const jwt_refresh_url = 'api/v3/token/refresh?token=' + this.storage.user.token;
return this.http.getFromAccountsApi(jwt_refresh_url)
.pipe(tap((token) => {
this.storeJwtToken(token);
}));
}
private storeJwtToken(jwt: string) {
const user = this.storage.user;
user.token = jwt;
this.storage.user = user;
}
Btw. the reason I am not doing this inside catchError is because our backend is structured like it will always send HTTP status code 200 and inside that response they will send custom http code based on error such as 401, 500 or success such as 200 and etc. So it won't go inside catchError since it looks for HTTP status codes other than 200.
Now my issue is after implementing the inceptor now my API's getting called multiple times. See screenshot below:
Been stuck since yesterday and haven't found any proper solution yet. Would be great if anyone could point what I am doing here and how do I solve it?
If you have any further query, do let me know. Thank you..
A tip for:
Btw. the reason I am not doing this inside catchError is because our backend is structured like it will always send HTTP status code 200 and inside that response they will send custom http code based on error such as 401, 500 or success such as 200 and etc. So it won't go inside catchError since it looks for HTTP status codes other than 200.
You can do a map in the response from the server and check if theres an error, and then throw an error from there, then catchError should work on sequent pipes.
The error is because you are returning the handle in the switchMap making the request being called again.
return next.handle(req);
Change that line to:
return of(event)
And it should work
Before I updated to HttpClient, I used the Http class and loaded data using the following code.
get(): Promise<Account> {
return this.http.get(this.apiUrl)
.toPromise()
.then((response) => response.json())
.catch(this.handleError);
}
private handleError(error: any) {
// do nothing for unauthorized user, as should be handles on component
if (error.status != '401') {
console.error('An error occurred', error);
}
return Promise.reject(error._body || error);
}
If the server errored with a 500, or 400 (validation) i could return the text from the server via my .catch handler.
Now I have updated to use the HttpClient and my code has changed to this
get(): Promise<Account> {
return this.httpClient.get<Account>(this.apiUrl)
.toPromise()
.catch(this.handleError);
}
private handleError(error: any) {
// do nothing for unauthorized user, as should be handles on component
if (error.status != '401') {
console.error('An error occurred', error);
}
return Promise.reject(error._body || error);
}
As you can see only the get method has changed, but now I cannot get the _body of the error response?
How can I get the body of the response like I did before?
I use Aurelia Fetch Client library to fetch JSON data from the backend server by the code:
getData() {
let httpClient = new HttpClient();
return httpClient.fetch('http://localhost:9220/get-data')
.then(response => response.json())
.then(data => return data);
}
}
And the metod getData() is called from the another code by the code:
dataService.getData().then(data => {
this.data = data;
}).catch(error => {
this.backendError = true;
});
As you can see I use here a catch statement and in case of error it's called, but I also see in the console an error message that comes from the library: "vendor-bundle.js:1395 Unhandled rejection TypeError: Failed to fetch". How can I get rid it?
I'm unsure if this is a bug with the Aurelia HTTP Fetch Client, but adding a responseError interceptor should remove the Unhandled Exception warning in the console.
let http = new HttpClient();
http.configure(config => {
config.withInterceptor({
response(response) {
return response;
},
responseError(error) {
return error;
}
})
});
This error may also come from the UseDeveloperExceptionPage middleware in a .NET Core API. This middleware strips all headers from the response which create CORS issues and causes the "TypeError: Failed to fetch" error you saw. Here is an example of my solution, which is described in full here.
.NET Core Middleware
private static Task HandleExceptionAsync(HttpContext context, Exception exception)
{
var code = HttpStatusCode.InternalServerError;
var result = JsonConvert.SerializeObject(new { error = "An internal server error has occurred." });
context.Response.ContentType = "application/json";
context.Response.StatusCode = (int)code;
return context.Response.WriteAsync(result);
}
Aurelia Interceptor
responseError(response: any): Promise<Response> {
if (response instanceof Response) {
return response.json().then((serverError: ServerError) => {
// Do something with the error here.
return Promise.reject<Response>(serverError.error);
});
}
}