Observable - 401 causing forkJoin to error out - javascript

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

Related

Firebase callable function to read real time database

Here I am trying to access the user's data from real time database by providing the UID. I have tried so many things but none worked. I have followed the documentation but no luck I am keep getting error -
Sending back results [promise]
Another example for writing the data which I have followed to create my logic but it didn't worked -
exports.userData = functions.https.onCall((data, context) => {
// verify Firebase Auth ID token
if (!context.auth) {
return { message: 'Authentication Required!', code: 401 };
}
const userId = data.text;
const ref = database.ref('/USERS/' + userId);
return ref.on('value', (snapshot) => {
console.log(snapshot); /* <--- I have tried with this and without this none worked*/
})
.then(snapshot => {
return {
data: snapshot
};
}).catch((error) => {
throw new functions.https.HttpsError('unknown', error.message, error);
});
});
The error I get on client side is -
service.ts:160 POST https://us-central1-gokuapp.cloudfunctions.net/userData 500
error.ts:66 Uncaught (in promise) Error: INTERNAL
at new YN (error.ts:66)
at XN (error.ts:175)
at rC.<anonymous> (service.ts:231)
at tslib.es6.js:100
at Object.next (tslib.es6.js:81)
at r (tslib.es6.js:71)
Edit: Before, I was correctly writing the code on my end but, I was either getting the error or null object based on the changes that I made during the discovery process. Anyone who had faced the same problem, just remember this... "cloud functions takes time to warm up to get fully functional", even though I am really thankful to #Frank van Puffelen and #oug Stevenson for their input. :) :) :)
Don't use on() in Cloud Functions, since that attaches a persistent listener to a query (and it doesn't return a promise). Use once() instead to query data a single time and get a promise the resolves with a snapshot. Also you should use snapshot.val() to get a plain JavaScript object with the contents of the snapshot.
return ref.once('value') // use once() here
.then(snapshot => {
return {
data: snapshot.val() // also use val() here to get a JS object
};
})
.catch((error) => {
throw new functions.https.HttpsError('unknown', error.message, error);
});
Callable Cloud Functions can return any JSON data. Your snapshot variable is a DataSnapshot object however, which contains a lot more than just JSON data.
You're probably looking to return the snapshot's value:
.then(snapshot => {
return {
data: snapshot.val()
};

Angular / rxJS: combineLatest - how to handle 404 Errors

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

Rxjs sequence of ajax calls depending on results from previous ones and handling errors

can you help me with turning the following promise-based scenario into rxjs streams?
async function createItem(...) {
let newData, metadata;
try {
newData = await ajax('.../createItem', ...)
} catch (e) {
throw new Error('Can not create item', e);
}
try {
metadata = await ajax('.../createMetadata', newData.id, ...);
} catch (e) {
throw new Error('Item created but metadata not', newData, e);
}
return {newData, metadata}
}
Is's just the flow when you have two-staged item creation process.
For example, you first call ajax to create actual item, you get item id from the response
then you call another ajax to set some metadata of newly created item.
When success, you return the new metadata and new data. When saving actual item (first stage) fails, you get the error that neither actual object nor metadata was created.
If the actual object is created but metadata fails to save, you will get error that actual object was created however metadata not.
I am trying to implement that using Observable.concat, however I can't access the output from first ajax call in the second call.
I also tried with Observable.ajax(...).mergeMap(Observable.ajax(...))....
but then I don't know where the catch should be put to identify at which stage (1 or 2) the failure occured.
How you generally solve the problem, when you have sequence of events (where input of previous is needed for the next one) and want to produce full result combined from all ajax responses, or partial result combined with responses from first subsequent success ajax calls and the error message of the stage when it failed?
I'm assuming you're using RxJS 5.5 with pipable operators:
return ajax('.../createItem', ...)
.pipe(
catchError(e => {
throw new Error('Can not create item', e);
}),
concatMap(newData => ajax('.../createMetadata', newData.id, ...)
.pipe(
map(metadata => ({ newData, metadata })),
catchError(e => {
throw new Error('Item created but metadata not', newData, e);
}),
)
),
)

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.

Chaining two observables http and storage

I am trying to chain two observables. I'm working with the Ionic2 framework. The first observable tries to get an access token out of a mobile device and then second observable then calls the api with that access token. But for some reason the second observable never gets called even tho I subscribed to the chain.
What am I missing?
this.observableChain = Observable.fromPromise(this.storage.get('accessToken'))
.map(accessToken => this.http.post('http://1234/user/login', {'accessToken': accessToken}))
.subscribe(function(result) {
console.log(result);
}, function(error) {
console.log(error);
});
Thanks in advance.
When chaining observables you need to use flatMap().
Observable.fromPromise(this.storage.get('accessToken'))
.flatMap(accessToken =>
this.http.post('http://1234/user/login', {'accessToken': accessToken})
)
.subscribe(
result => console.log(result),
error => console.log(error)
);
A good explanation of this concept is found here - http://reactivex.io/documentation/operators/flatmap.html
You have subscribed, but there is no such thing as subscribing to the chain. Your first observable emits an observable, which is never subscribed to. Basically you have to do something like this:
obs1.subscribe(obs2 => obs2.subscribe(result => ...))
HTH

Categories