Passing Multiple Values Through Pipe While Making Dependent HTTP Requests without Using Closures - rxjs

Is there way to avoid storing a result using closures from an initial HTTP request for use in a second HTTP request, as well as, in the subscribe block? The second request depends on the first so I can't use combineLatest, etc.
let acceptedOrgAgreement = false;
// Initial HTTP request
this.organizationResource
.getOrganizationById(organizationId)
.pipe(
// Initial HTTP response
map((organization: Organization) => !!organization.acceptedAgreementDate),
exhaustMap((hasSignedOrgAgreement: boolean) => {
// Would rather not have to temporarily store this in outer function scope
acceptedOrgAgreement = hasSignedOrgAgreement;
// Second HTTP request that relies on the initial request
return this.siteResource.updateSite(payload, hasSignedOrgAgreement);
}),
// Pass first and second HTTP responses to subscribe as tuple
map((response: any) => [response, acceptedOrgAgreement])
)
.subscribe(([response, hasSignedOrgAgreement]: [any, boolean]) => {
// Do some work using both responses
});

what about return a forkJoin ?
this.organizationResource
.getOrganizationById(organizationId)
.pipe(
map((organization: Organization) => !!organization.acceptedAgreementDate),
exhaustMap((hasSignedOrgAgreement: boolean) => {
return forkJoin([this.siteResource.updateSite(payload, hasSignedOrgAgreement), of(hasSignedOrgAgreement)]);
}),
)
.subscribe(([response, hasSignedOrgAgreement]: [any, boolean]) => {
});

Related

How to make cypress wait for a response that depends on another response?

From response A (/list.json) my app receives a list of items. Based on the output of A, my app makes another set of requests B for individual items (/one.txt, /two.txt, ...).
Now in my test I want to make sure that all responses B return HTTP 200.
Waiting (cy.wait) for response A is fine. However, waiting for responses B is more difficult, because I have to start waiting just upon receiving response A where I learn about responses B.
I tried 2 options:
start waiting inside of cy.wait of response A - code,
start waiting outside of cy.wait of response A - code
Neither of those work. With option 1 I get
`cy.wait()` timed out waiting `5000ms` for the 1st request to the route: `one.txt`. No request ever occurred
And with option 2 I get a pass, even though /two.txt doesn't exist. Looks like cy.wait for responses B is added after the responses were received
Since all requests are triggered off the visit, and are dynamic, you need a single intercept that handles all requests.
To me that means adding some javascript and dynamic aliases.
// avoid status code 304, disable browser cache
Cypress.automation('remote:debugger:protocol', {
command: 'Network.clearBrowserCache'
})
describe('spec', () => {
it('test', () => {
let items = [];
cy.intercept('GET', '*', (req) => {
const slug = req.url.substring(req.url.lastIndexOf('/') + 1)
if (slug === 'list.json') {
req.alias = 'list'
}
if (items.includes(slug)) {
req.alias = 'item'
}
req.continue((res) => {
if (slug === 'list.json')) {
items = res.body;
}
})
})
cy.visit('https://demo-cypress.netlify.app');
cy.wait('#list') // wait for list
.then(() => { // now items is populated
for (let item of items) { // really just need the count
cy.wait('#item').then(interception => { // wait n-times
expect(interception.response.statusCode).to.eq(200);
})
}
})
})
})

how can I do a switchMap and get the diferent responses in the subscription?

How can I achieve the following using RXJS?
I have 3 requests each dependant on the previous, and I need all responses in the subscription.
this.httpService.request1(paramsObj).pipe(
switchMap((response)=>{
return this.httpService.request2({...response})
}),
switchMap((response)=>{
return this.httpService.request3({...response})
})
).subscribe(([response1, response2, response3]) => {
// somehow access all responses here while each response is dependeant on the previous one
})
You probably have to mannually pass it down like below
this.httpService.request1(paramsObj).pipe(
switchMap((res1)=>{
return this.httpService.request2({...res1}).pipe(map(res2=>[res1,res2]))
}),
switchMap(([res1,res2)=>{
return this.httpService.request3({...res2}).pipe(map(res3=>[res1,res2,res3]))
})
).subscribe(([res1, res2, res3]) => {
})

Unit testing NestJS Observable Http Retry

I'm making a request to a 3rd party API via NestJS's built in HttpService. I'm trying to simulate a scenario where the initial call to one of this api's endpoints might return an empty array on the first try. I'd like to use RxJS's retryWhen to hit the api again after a delay of 1 second. I'm currently unable to get the unit test to mock the second response however:
it('Retries view account status if needed', (done) => {
jest.spyOn(httpService, 'post')
.mockReturnValueOnce(of(failView)) // mock gets stuck on returning this value
.mockReturnValueOnce(of(successfulView));
const accountId = '0812081208';
const batchNo = '39cba402-bfa9-424c-b265-1c98204df7ea';
const response =client.viewAccountStatus(accountId, batchNo);
response.subscribe(
data => {
expect(data[0].accountNo)
.toBe('0812081208');
expect(data[0].companyName)
.toBe('Some company name');
done();
},
)
});
My implementation is:
viewAccountStatus(accountId: string, batchNo: string): Observable<any> {
const verificationRequest = new VerificationRequest();
verificationRequest.accountNo = accountId;
verificationRequest.batchNo = batchNo;
this.logger.debug(`Calling 3rd party service with batchNo: ${batchNo}`);
const config = {
headers: {
'Content-Type': 'application/json',
},
};
const response = this.httpService.post(url, verificationRequest, config)
.pipe(
map(res => {
console.log(res.data); // always empty
if (res.status >= 400) {
throw new HttpException(res.statusText, res.status);
}
if (!res.data.length) {
this.logger.debug('Response was empty');
throw new HttpException('Account not found', 404);
}
return res.data;
}),
retryWhen(errors => {
this.logger.debug(`Retrying accountId: ${accountId}`);
// It's entirely possible the first call will return an empty array
// So we retry with a backoff
return errors.pipe(
delayWhen(() => timer(1000)),
take(1),
);
}),
);
return response;
}
When logging from inside the initial map, I can see that the array is always empty. It's as if the second mocked value never happens. Perhaps I also have a solid misunderstanding of how observables work and I should somehow be trying to assert against the SECOND value that gets emitted? Regardless, when the observable retries, we should be seeing that second mocked value, right?
I'm also getting
: Timeout - Async callback was not invoked within the 5000ms timeout specified by jest.setTimeout.Timeout - Async callback was not invoked within the 5000ms timeout specified by jest.setTimeout.Error:
On each run... so I'm guessing I'm not calling done() in the right place.
I think the problem is that retryWhen(notifier) will resubscribe to the same source when its notifier emits.
Meaning that if you have
new Observable(s => {
s.next(1);
s.next(2);
s.error(new Error('err!'));
}).pipe(
retryWhen(/* ... */)
)
The callback will be invoked every time the source is re-subscribed. In your example, it will call the logic which is responsible for sending the request, but it won't call the post method again.
The source could be thought of as the Observable's callback: s => { ... }.
What I think you'll have to do is to conditionally choose the source, based on whether the error took place or not.
Maybe you could use mockImplementation:
let hasErr = false;
jest.spyOn(httpService, 'post')
.mockImplementation(
() => hasErr ? of(successView) : (hasErr = true, of(failView))
)
Edit
I think the above does not do anything different, where's what I think mockImplementation should look like:
let err = false;
mockImplementation(
() => new Observable(s => {
if (err) {
s.next(success)
}
else {
err = true;
s.next(fail)
}
})
)

How to modify an Effect to work with multiple requests called?

I've written this Effect to handle one call at a time:
#Effect()
indexCollectiveDocuments$ = this.actions$.pipe(
ofType<IndexCollectiveDocuments>(CollectiveIndexingActionTypes.IndexCollectiveDocuments),
mergeMapTo(this.store.select(getIndexingRequest)),
exhaustMap((request: any[], index: number) => {
return zip(...request.map(item => {
this.currentItem = item;
return this.indexingService.indexDocuments(item).pipe(
map((response: any[]) => new IndexCollectiveDocumentsSuccess(response)),
catchError(error => of(new IndexCollectiveDocumentsFailure({ error: error, item: this.currentItem })))
)
}))
})
);
It does dispatch both Success and Failure actions according to the request result.
But when I feed the effect with multiple items(which are the payload of getIndexingRequest) to send requests one after another, the Success and Failure actions are not dispatched accordingly, cancels when one is failed.
How do I modify it so that it works with multiple requests, rather than one?
EDIT: I can see all the requests and their results in the network tab. But only one action is dispatched.
You're using the RxJS operator exhaustMap which will cancel incoming requests when there is already one request running.
To run all requests use either mergeMap (run parallel) or concatMap (run serial).

RxJS calling second operation when first is successful

I'm using Angular2 and rxjs.
I have an operation called login(). This will use a http.post request to send the authentication details to the server and will then receive a token back.
It needs to read the result and if the token is received successfully it will do some operations to validate the token and decode it, and if all of this is OK then it will send the username from the token to the server with a http.get and retrieve the user's details.
I would like all of the above to be returned as one Observable, but I'm scratching my head as to how two operations that should occur one after the other should be structured using the RxJS way.
I don't think subscribing to the first operation and then calling the second operation inside the first is the "right" way, because then how do you capture a failure in the first one.
Something like this?
this.http.post('http://localhost/auth/token', creds, {
headers: headers
})
.map(res => res.json())
.do(
// validate token
// decode token
)
.thenDo(
// get user details
this.http.get(url, options)
.map(res => res.json())
.do(
//save user and token in localStorage
)
)
i dont know much about Rxjs do and thenDo function but yes you can do like this
this.http.post('http://localhost/auth/token', creds, {
headers: headers
})
.map(res => {
return [{status: res.status , json: res.json()}]
})
.subscribe(res=>{
if(res[0].status == 200){ // do you action depends on status code you got assuming 200 for OK response
this.validateToken() // Validate your token here in some method named as validateToken
this.decodeToken() // decode token here in this method
this.getUserDetail() //if everything worked fine call your another get request in another method
}
},
err => {
console.log(err, err.status) //catch your error here
})
getUserDetail(){
// make http get request for user detail and saveing into locastroage
}
Using flatMap is a good way to chain operations that each return a new Promise or Observable. Each time we need to map over a function that returns a Promise or Observable, we can use flatMap to construct a stream that emits the resolved data. Here we construct an Observable of user data, and finally we can subscribe to it (to save to localstorage, etc).
I've assumed your validation code is just some function that returns a Promise or Observable.
const options = { headers };
const user$ = this.http.post('http://localhost/auth/token', creds, options)
.map(res => res.json())
.flatMap(validationFunctionThatReturnsAPromise)
.flatMap(authResponse => {
// get user details
return this.http.get(url, options).map(res => res.json());
});
user$.subscribe(user => /** do something with the user data **/);

Resources