Multiple Observables waiting for single Observable completion on RetryWhen - rxjs

I am trying to build an app using Oauth2.
There are multiple (GET) calls to API from the frontend and I deal with them like so:
// Call API for data
this.apiGet('/foo?id=bar).pipe(
// If it fails, use refresh token to obtain new access token and try again
retryWhen(x => {
// If there is existing request (Observable), use it
if (this.refreshTokenObservable !== null) {
return this.refreshTokenObservable;
}
// Otherwise make new request (fills this.refreshTokenObservable)
return this.getAccessByRefreshToken();
})
)
This works fine when there is only one apiGet(), however, when using multiple calls one right after another, the refreshTokenObservable variable do not fill quickly enough, causing multiple calls and, subsequently, errors.
Is there any way to prevent this?

Related

Update cached data using rxjs publishReplay: Angular 5

I have a list of users that I want to cache so that different component in my Angular 5 app does not hit the web service, and rather return cached response. To do this I did the following:
getAllUsers() {
return this.getUncachedUsersList().publishReplay().refCount();
}
getUncachedUsersList() {
return this.http.get('https://......');
}
In the above code snippet, I have two methods. I call getAllUsers inside all the components that needs users list, except in the case where let say I am adding a user and then I need an updated list. In that case I call 'getUncachedUsersList' to get the latest.
The problem is, when I call 'getUncachedUsersList', I expect 'getAllUsers' to cache the new list, but instead it return the same old list that was cached before adding a new user. So I would like to know how can I clear the cached response and save the new response that I get from 'getUncachedUsersList' and return the new response when 'getAllUsers' is called.
Rathar than doing like this, you should considering maintain a cacheable Subject.
// behavior subject do cache the latest result
// each subscribe to userList$ get the latest
userList$ = new BehaviorSubject([]);
// each time getNewUserList get call
// userList$ get the new list by calling next
getNewUserList() {
this.http.get(`http://...`).subscribe(list => this.userList$.next(list));
}

Intercept observables before subscription callback

I am using the following code to make get request:
makeGetReq$(url:string):Observable{
let getReqObservable;
getReqObservable = this.httpClient.get(url) //code for making get request
return getReqObservable
}
The problem is sometimes my backend might return {error:true, message} with status code 200. (I know thats weird).In that case I want to intecept getReqObservable and not allow its subscription callback to run.
image.component.ts
makeGetReq$(url:string):Observable{
let getReqObservable;
getReqObservable = this.httpClient.get(url)//code for making get request
return getReqObservable
.do((value)=>{
if(value.error){
//do not allow it to propagate further
})
})
You should propagate it further, but as an error rather than an event (i.e. do just like if your backend did the right thing and returned an error response):
makeGetReq$(url: string): Observable<Something> {
return this.httpClient.get<Something>(url).pipe(
mergeMap(value => value.error ? throwError(value) : of(value))
);
}
Otherwise, the calling method has no way to know that an error occurred, and thus can't execute the callbacks it might have registered for errors.
The easiest would probably be filter.
Filter items emitted by the source Observable by only emitting those that satisfy a specified predicate.
It would look like this:
return getReqObservable
.filter(value => !value.error)
It was pointed out, that you lose the notification completely if you just filter out the error case. There is of course the option to create a RxJS error notification with throwError, but it is also possible to just subscribe to the same source observable a second time with a different filter condition.
Be careful however to not call the backend twice, e.g. by using share.

Access store from service (or where I should be accessing the store)?

I'm only a few days in (transitioning from Ember) so please pardon my ignorance.
I have an array of object (profileaccounts) in my store.
I have many different components which are connected to the store and have code like below, sometimes the user is the person logged in, sometimes its a user that is being passed into the component (when someone looks at someone else's profile)
componentDidMount() {
let user = this.props.user;
let account = this.props.profiles.find(function (prof) { return prof.profile.id === user.id });
if (account == null) {
this.props.dispatch(userActions.getProfile(user.id));
}
}
This completely works but I don't want this code replicated over and over again. My gut feeling is that I should always call .getProfile(user.id) and its the job of the actions to determine if the data exist in the local cache (store) or does it needs to be added. If it needs to be added, add it, then either way return it.
Alternatively, maybe the user service (which represents the API and is called by the actions to populate the profiles) is supposed to look locally before it calls the API. Regardless, I don't know how (or if I should) to access the store from the actions or service, only from the connected component.
I haven't seen this scenario in any of the guides I've read about redux/react, so if anyone can include a resource to where I should be looking I'd appreciate it. If I'm totally going about this the wrong way, I'd be happy to know that too.
You can use redux-thunk to access state inside the action
link to thunk: https://github.com/reduxjs/redux-thunk
The code would be like this:
function incrementIfOdd() {
return (dispatch, getState) => {
const { counter } = getState();
if (counter % 2 === 0) {
return;
}
dispatch(increment());
};
}
Using thunk you'll be able to access state inside the action.
Your approach sounds good...You'll start with dispatching an action "dispatch(getProfile(userId))" then inside the action you'll do whatever you want and when you finally have the data you want to put in the store dispatch another action "dispatch(storeUserProfile(profile))" which will put the data in the store via reducer.

mocha: can't use one request for mutlple `it` test

const request = require('supertest');
const server = request('http://localhost:9001');
describe('Get /static/component-list.json', function() {
const api = server.get('/static/component-list.json');
it('should response a json', function(done) {
api.expect('Content-Type', /json/, done);
});
it('200', function(done) {
api.expect(200, done); // This will failed
// server.get('/static/component-list.json').expect(200, done); // This will successed
});
});
when reuse api in the second test case, mocha will raise a Error:
The result of mocha test/api command:
How can I request the url once and use in multiple it case.
Solution
You have to create a new request for each test (each it) that you want to run. You cannot reuse the same request for multiple tests. So
describe('Get /static/component-list.json', function() {
let api;
beforeEach(() => {
api = server.get('/static/component-list.json');
});
Or if you want to reduce the number of requests made, then combine all your checks on the request into a single Mocha test.
Explanation
If you look at the code of supertest, you'll see that when you call an expect method with a callback, expect calls automatically calls end. So this:
api.expect('Content-Type', /json/, done);
is equivalent to this:
api.expect('Content-Type', /json/).end(done);
The end method is provided by superagent, which is what supertest uses to perform requests. The end method is what kicks off the request. It means you are done setting up the request and want to fire it off now.
The end method calls the request method, which is tasked with using Node's networking machinery to produce a Node request object that is used to perform the network operation. The problem is that request caches the Node request is produces but this Node request object is not reusable. So ultimately, a superagent or supertest request cannot be ended twice. You have to reissue the request for each test.
(You could manually flush the cached object between tests by doing api.req = undefined. But I strongly advise against this. For one thing, whatever optimization you might think you'd get is minimal because the network request still has to be made anew. Secondly, this amounts to messing with superagent's internals. It may break with a future release. Third, there may be other variables that hold state that might need to be reset together with req.)

Making an Ajax request using data from previous ajax request

I am attempting to write an Angular page to communicate with my Nodejs server, but I have ran into a snag.
I need to use multiple Ajax requests that rely on the data from previous ajax requests to work.
So Ajax request #1 provides data that is used by all other Ajax requests, and Ajax request #2 uses data from ajax request #1 to get the data that Ajax request #3 needs.
Since Angular is asynchronous, how can I make my script wait for the data from the first one before making the next ajax call.
id = ajax()
Wait for data
token = ajax(id)
wait for data
gametoken = ajax(id, token)
wait for data
Chandermani is correct, just remember to make sure to make the variables you need available in the scope that you need it.
var id,token,gametoken;
$http.get('http://host.com/first')
.then(function(result){
id=result;
return $http.get('http://host.com/second/'+id);
}
.then(function(result){
token = result
return $http.get('http://host.com/third'+id+'/'+token);
}
.then(function(result){
gametoken = result;
//Do other code here that requires id,token and gametoken
}
EDIT:
You don't have to chain the promises. If you want to make a call at a later date and you want to make sure the promises have resolved you can use $q.all();
var id,token,gametoken;
var p1 = $http.get('http://host.com/first')
.then(function(result){
id=result;
}
// Later on to make your new second call
$q.all([p1]).then(function(){
//Make second call knowing that the first has finished.
}
$q.all() takes an array so you can put in multiple promises if you want and it will wait until they have all resolved.

Resources