Angular / rxJS: combineLatest - how to handle 404 Errors - javascript

I need to load multiple Translation files via HTTP and sometimes it can happen, that a file will not be available, so therefore it will return a 404 error. My problem ist, that if one 404 error occours, the complete translation loading fails. How can i load all translation files which are available?
The following code example works, when all files are available:
public getTranslation(lang: string): any {
return Observable.combineLatest(this.resources.map(config => {
return this.http.get(url);
})
).map(response => {
return response.reduce((a, b) => {
return Object.assign(a, b);
})
})}

The answer is the rxjs operators retry/ retryWhen
Also, there are many ways to do some error handling. The simplest one, of course is to get the error and console log it in the http call (This can be done in the service or in whatever module you use). One example would be:
In the service:
public getWhatever() {
let url = `https://route_to_the_endpoint`;
return this.http.get(url, { headers: this.commonHeaders });
}
Un the component:
this.myservice
.getWhatever()
.subscribe(
(response: any) => {
/*Do whatever here*/
},
(error) => {
console.log(error);
/* Prepare some retry code here */
}
);
Error can also be processed in the service.
Other way to manage the error is to use rxjs pipes, for instance "catch/ catchError":
https://www.learnrxjs.io/learn-rxjs/operators/error_handling/catch
From what I see in your question, "retry"/ "retryWhen" to make sure the translation files reload when you get an error. The information for these pipes:
https://www.learnrxjs.io/learn-rxjs/operators/error_handling/retry
https://www.learnrxjs.io/learn-rxjs/operators/error_handling/retrywhen
You can use also rxjs "take(1)" in REST, as there is only one response.
Interceptors can be used to process error handling for all calls. But different calls may need different error handling.

CombineLastest of observables with catchError to return null
return Observable.combineLatest(this.resources.map(config => {
return this.http.get(url).pipe(
catchError(_=>of(null))
);
})
)

Related

Error catched by then instead of catch of Promise - React Native

I am having a problem when calling two async functions in React Native.
Here is code (I just changed the names):
getItems()
.then((response) => {
setItems(response.data);
})
.catch((err) => console.log('getItems', err));
getOtherItems()
.then((response) => {
console.log('response.data', response.data);
setOtherItems(response.data);
})
.catch((err) => console.log('getOtherItems', err));
When the first function throws an error, the error is correctly catched by the catch. But, when the second functions breaks, the error passes through then instead of catch.
The logs are like this:
getItems <Error>
response.data <Error>
Any idea why this is happening?
EDIT:
getItems and getOtherItems make an HTTP call using axios to an external service that returns an array of items.
EDIT 2:
Code for getItems and getOtherItems:
getItems() {
return axios.get('an URL');
}
getOtherItems() {
return axios.get('another URL');
}
EDIT 3:
I realized the first function throws a 500 error while the second logs a 404. So, the problem may be with how the services return the error.
EDIT 4:
It seems the problem is an error in the service. I'll let you know once it's confirmed. Thanks!

Issue using Axios with Scryfall API

Attempting to use axios to make a get request at the following endpoint, and I keep getting errors:
When I check it using Postman and in a browser (GET request), it returns data just fine, but otherwise I can’t get a response.
This is the call I’m using, I don’t know if it’s some sort of issue with my code or with axios itself:
axios.get(`https://api.scryfall.com/cards/named?exact=${args.name}`)
.then((res) => {
console.log(JSON.stringify(res));
})
.catch((err) => {
if (err.response) {
throw new Error(`Card with name (${name}) not found!`)
}
throw new Error(`Could not complete that query!`)
})
The argument args.name is passed as part of a GraphQL resolver, and it definitely has a value, so not sure what the deal is.
Any help would be greatly appreciated!
There are a couple of problems here.
Generally it's not a good idea to throw new errors after catching the original error. As written, your code throws an additional error because the axios Promise is thrown again instead of being dealt with inside catch - so node will complain that you didn't resolve or reject the promise.
The substantive issue is the same as the one answered here except for res - the actual error is TypeError: Converting circular structure to JSON which is caused by trying to JSON.stringify the res object:
JSON doesn't accept circular objects - objects which reference themselves. JSON.stringify() will throw an error if it comes across one of these.
The request (req) object is circular by nature - Node does that.
In this case, because you just need to log it to the console, you can use the console's native stringifying and avoid using JSON
So you can fix this by changing your code to:
axios.get(`https://api.scryfall.com/cards/named?exact=${args.name}`)
.then((res) => {
console.log(res)
})
.catch((err) => {
console.log(err)
if (err.response) {
console.error(`Card with name (${name}) not found!`)
} else {
console.error(`Could not complete that query!`)
}
})
If you were just using console.log for testing/as an example and actually need to stringify the data to use some other way, just make sure you're stringifying the data (which is presumably what you actually want) and not the whole res object:
axios.get(`https://api.scryfall.com/cards/named?exact=${args.name}`)
.then((res) => {
let scryfallData = JSON.stringify(res.data)
doSomethingWith(scryfallData)
})

Handling errors in an async/await Angular HttpClient method

I am attempting to use an async/await pattern in order to handle a scenario that might be considered "callback hell" if implemented otherwise.
Here is an extremely dumbed down version of the code. The real code has about 5 conditional HttpClient calls based on the data from the first call (not my api...) which is the reason why I am using the async/await pattern in the first place.
async blah(): Promise<boolean> {
try {
let resp = await this.http.get("https://httpstat.us/500").toPromise();
console.warn("you should not see this");
// the real code will logically call the api multiple times based on conditonal data from resp
// hence the attempted usage of async/await to avoid "callback hell"
// blah() will eventually return an object.
return true;
}
catch (err) {
console.error("caught inside blah()");
throw err;
}
}
ionViewDidLoad() {
this.blah().then(data => {
console.warn('okokokok');
}).catch(error => {
console.error(error)
});
}
What happens, I can see the call actually 500, but the code continues and the following is printed to the console:
polyfills.js:3 GET https://httpstat.us/500/ 500 (Internal Server Error)
main.js:927 you should not see this
main.js:940 okokokok
As you can see, it isn't catching the 500 (or any other http status I have tested with)
The device I am testing with is a Pixel 2 running P and the console data is coming from a Chrome device inspector session.
Any advice would be greatly appreciated.
** Edit: This is clearly an issue with the combination of ionic and angular... It should work...
** Edit: it turns out to 100% be an Angular issue... Not the framework itself but how an interceptor was implemented. I will leave this here instead of deleting the question in the rare case someone else requires it.
If i uderstood your question correctly, you want to do cascade calls, so you make the http request and based on the response you want to do another http call. If that is the case, then you should consider using switchMap operator:
this.http.get("https://httpstat.us/500").pipe(
switchMap( result => {
if(result.a === 5) {
return this.http.get("some server api url");
}
return return this.http.get("another server api url");
})
)
You handle the errors then in rxjs way.
See cascading calls

RxJS retry entire chain

I read images from a live stream and select a batch periodically.
I then send them to the server for validation. A HTTP error will be thrown if any fail validation. If that occurs I want to get a new batch of images.
this.input.getImages()
.throttleTime(500)
.switchMap(image =>
new Observable<{}>(observer => {
// Some operation
})
.map(i => ({ image, i }))
).filter(({ i }) => {
// some filtering
})
.map(({ image }) => image)
.take(6)
.bufferCount(6)
.map(images => // switch map??
Observable.fromPromise(this.server.validate(images))
)
.retry(2) // This only retrys the request, I want it to retry the whole chain (to get valid images)
.subscribe(images => {
console.log('All done')
},
err => {console.log(err)}
)
The problem I'm having is that only the HTTP request gets retried since that is the new observable. There must be some way to encapsulate the beginning of the chain into a single Observable?
See learnrxjs - retry. The example shows everything restarting from source onwards when an error is thrown.
The page shows pipe syntax, but the JSBin shows fluid operator syntax if you prefer.
The basic pattern is
const retryMe = this.input.getImages()
.flatMap(val => {
Observable.of(val)
// more operators
})
.retry(2);
Simple way is to wrap your complex observable in defer ans use retry on resulting observable.

Observable - 401 causing forkJoin to error out

I am using forkJoin to make several server requests. This is a pattern I have commonly been using through out my application and it has been working great. However we just started implementing user roles which is done on the backend. I am not sure what is the best practice for implementing roles as I am mostly a front end developer, nonetheless this is the problem I have encountered:
Our application has member and admin member roles.
From each view I must make calls to the backend for both member and admin member roles regardless as roles are not determined on the frontend.
Member data is always returned in for both roles as members and admin members both have personal data.
Requests made for admin data is only returned when the user is an admin. Whenever the user does not have admin access the request returns a 401 error. This is where I am having a problem.
Whenever the call returns a 401, the error method in my subscribe method is invoked and I do not have access to any of the calls that were made including the calls associated to the member data.
In my included code within the forkJoin there are five calls passed into the method. The third and forth call only return data if the user is an admin while the rest of the calls are always returned for either member or admin.
When the user is not an admin the third call returns a 401 and the stream stops and the error handler in my subscribe method is invoked. This is obviously not what I want. I want the stream to continue so I can use the data in the _data method.
I have only been using RXJS for 6 months and am learning. Maybe I should be using a different pattern or maybe there is a way to fix this. Any help with code examples would be greatly appreciated. Below my code example I included another example of code in which I attempted to fix the problem by playing around with catch methods. It didn't work.
My View get method:
private getZone() {
this.spinner.show();
this.zonesService.getZone(this.zoneId)
.map(response => {
this.zone = response['group'];
return this.zone;
})
.flatMap(() => {
return Observable.forkJoin(
this.teamsService.getTeam(this.zone['TeamId']),
this.zonesService.getZoneAssociations(this.zone['id'], '/myDevices'),
this.zonesService.getZoneAssociations(this.zone['id'], '/devices'),
this.zonesService.getZoneAssociations(this.zone['id'], '/groupMembers'),
this.sitesService.getSite(this.zone['SiteId'])
);
})
.subscribe(
_data => {
// data handling...
},
_error => {
// error handling ...
}
);
}
My attempt to fix:
private getZone() {
this.spinner.show();
this.zonesService.getZone(this.zoneId)
.map(response => {
this.zone = response['group'];
return this.zone;
})
.flatMap(() => {
return Observable.forkJoin(
this.teamsService.getTeam(this.zone['TeamId']),
this.zonesService.getZoneAssociations(this.zone['id'], '/myDevices'),
this.zonesService.getZoneAssociations(this.zone['id'], '/devices')
.catch(error => Observable.throw(error)),
this.zonesService.getZoneAssociations(this.zone['id'], '/groupMembers')
.catch(error => Observable.throw(error)),
this.sitesService.getSite(this.zone['SiteId'])
);
})
.subscribe(
_data => {
// data handling...
},
_error => {
// error handling...
}
);
}
Returning Observable.throw will just rethrow the caught error, which will see forkJoin emit the error.
Instead, you could use Observable.of(null) to emit null and then complete, which will see forkJoin emit a null for the observable that emitted the error:
return Observable.forkJoin(
this.teamsService.getTeam(this.zone['TeamId']),
this.zonesService.getZoneAssociations(this.zone['id'], '/myDevices'),
this.zonesService.getZoneAssociations(this.zone['id'], '/devices')
.catch(error => Observable.of(null)),
this.zonesService.getZoneAssociations(this.zone['id'], '/groupMembers')
.catch(error => Observable.of(null)),
this.sitesService.getSite(this.zone['SiteId'])
);
Or, if you wanted to emit the error as a value, you could use Observable.of(error).

Categories