RxJS Pipe Not Called With zip - rxjs

I'm zipping a three observables, each of the three observables has it's own "success" callback using .pipe(tap() => {...});. This works fine when all three observables execute without error, but if one of the observables errors out, then none of the tap methods execute. How can I have the tap methods always execute if that observable runs successfully?
var request1 = Observable.create(...); //Pretend this one will fail (though request2 or request3 could also fail)
var request2 = Observable.create(...);
var request3 = Observable.create(...);
request1.pipe(tap(() => {
//Unique success callback should always run if request1 succeeds, even if request2 or request 3 fails.
}));
request2.pipe(tap(() => {
//Unique success callback should always run if request2 succeeds, even if request1 or request 3 fails.
}));
request3.pipe(tap(() => {
//Unique success callback should always run if request3 succeeds, even if request1 or request 2 fails.
}));
var observable = zip(request1, request2, request3);
observable.subscribe(() => {
//Do something when all three execute successfully
});

I believe this is expected and the appropriate behavior for what you're dealing with. You probably want to look at using piping the catchError lettable into each of your requests and returning an empty observable.
request1.pipe(tap(() => {
//Unique success callback
}), catchError((err) => {
return empty();
}));
That way you handle the error of that observable without breaking the new zip.

#MichaelSolati is nearly correct, except
you should catch and return a value, e.g null if you wish to see output in subscribe() - because zip won't fire with empty()
your tap() callbacks are not in the zip() pipeline, they are separate branches which are not subscribed to, so are never activated.
Note that with changes to rxjs versions, the imports can get a bit tricky, for example zip is available as a function and as an operator.
With the rxjs.umd.js cdn used in the snippet below, I initially used the operator by mistake and it doesn't throw an error (but does not work).
I note from comments above you are doing this in an Angular context. If
you still have a problem, please post your full Angular module with imports and we can resolve the difficulty.
console.clear()
//console.log(rxjs)
// Get the operators and creators
const tap = rxjs.operators.tap
const empty = rxjs.empty
const zip = rxjs.zip
const catchError = rxjs.operators.catchError
const of = rxjs.of
const throwError = rxjs.throwError
//var request1 = of(1)
var request1 = throwError('error')
var request2 = of(2)
var request3 = of(3)
var req1 = request1.pipe(
tap(() => console.log('request1')),
catchError((err) => {
console.log('request1 has error')
return of(null)
})
);
var req2 = request2.pipe(
tap(() => { console.log('request2');})
);
var req3 = request3.pipe(
tap(() => { console.log('request3');})
);
var myObservable = rxjs.zip(req1, req2, req3);
myObservable.subscribe(
result => { console.log('result', result) }
);
<script src="https://cdnjs.cloudflare.com/ajax/libs/rxjs/6.2.0/rxjs.umd.js"></script>

Add catch block in your code where you can track the errors.
request1.pipe(tap(() => {
//Unique success callback I want to run if request2 succeeds.
//It should still run if request1 fails
}),
catchError(// Error occured)
);

Related

Expecting a Promise *not* to complete, in Jest

I have the following need to test whether something does not happen.
While testing something like that may be worth a discussion (how long wait is long enough?), I hope there would exist a better way in Jest to integrate with test timeouts. So far, I haven't found one, but let's begin with the test.
test ('User information is not distributed to a project where the user is not a member', async () => {
// Write in 'userInfo' -> should NOT turn up in project 1.
//
await collection("userInfo").doc("xyz").set({ displayName: "blah", photoURL: "https://no-such.png" });
// (firebase-jest-testing 0.0.3-beta.3)
await expect( eventually("projects/1/userInfo/xyz", o => !!o, 800 /*ms*/) ).resolves.toBeUndefined();
// ideally:
//await expect(prom).not.toComplete; // ..but with cancelling such a promise
}, 9999 /*ms*/ );
The eventually returns a Promise and I'd like to check that:
within the test's normal timeout...
such a Promise does not complete (resolve or reject)
Jest provides .resolves and .rejects but nothing that would combine the two.
Can I create the anticipated .not.toComplete using some Jest extension mechanism?
Can I create a "run just before the test would time out" (with ability to make the test pass or fail) trigger?
I think the 2. suggestion might turn handy, and can create a feature request for such, but let's see what comments this gets..
Edit: There's a further complexity in that JS Promises cannot be cancelled from outside (but they can time out, from within).
I eventually solved this with a custom matcher:
/*
* test-fns/matchers/timesOut.js
*
* Usage:
* <<
* expect(prom).timesOut(500);
* <<
*/
import { expect } from '#jest/globals'
expect.extend({
async timesOut(prom, ms) { // (Promise of any, number) => { message: () => string, pass: boolean }
// Wait for either 'prom' to complete, or a timeout.
//
const [resolved,error] = await Promise.race([ prom, timeoutMs(ms) ])
.then(x => [x])
.catch(err => [undefined,err] );
const pass = (resolved === TIMED_OUT);
return pass ? {
message: () => `expected not to time out in ${ms}ms`,
pass: true
} : {
message: () => `expected to time out in ${ms}ms, but ${ error ? `rejected with ${error}`:`resolved with ${resolved}` }`,
pass: false
}
}
})
const timeoutMs = (ms) => new Promise((resolve) => { setTimeout(resolve, ms); })
.then( _ => TIMED_OUT);
const TIMED_OUT = Symbol()
source
The good side is, this can be added to any Jest project.
The down side is, one needs to separately mention the delay (and guarantee Jest's time out does not happen before).
Makes the question's code become:
await expect( eventually("projects/1/userInfo/xyz") ).timesOut(300)
Note for Firebase users:
Jest does not exit to OS level if Firestore JS SDK client listeners are still active. You can prevent it by unsubscribing to them in afterAll - but this means keeping track of which listeners are alive and which not. The firebase-jest-testing library does this for you, under the hood. Also, this will eventually ;) get fixed by Firebase.

rxjs return cached response based on condition

I would like to create an observable which loads data from backend. However, I would like to tell him to really fetch it or take the results from cache.
backendData$ = http.get(...).pipe(shareReplay(1));
refresh$: Subject<{doBackendRequest: boolean}> = new Subject();
rowData$ = this.refresh$.pipe(
tap(opts => console.log(opts.doBackendRequest)),
switchMap(() => {
if (opts.doBackendRequest) {
// lets do a new request
return backendData$;
} else {
// somehow return cached results
return CACHED_DATA;
}
})
The rowData$ is subscribed by an async pipe rowData$ | async
Here is how I would like to call it:
this.refresh$.next({performBackendRequest: true});
this.refresh$.next({performBackendRequest: false});
The question now is how can I cache the http result? I tried to use shareReplay(1) but I can not find a working solution. Can you point me the direction?
Here's an example of how it might be implemented:
backendData$ = http.get(...);
cachedData$ = backendData$.pipe(shareReplay(1));
refresh$: Subject<boolean> = new Subject();
rowData$ = this.refresh$
.pipe(switchMap(refresh => refresh ? backendData$ : cachedData$))

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

Promise resolves before looping calls to provider finish

I'm running into an issue with some code for an Ionic 3 app.
Basically, I have a list of objects that all have a unique id. The unique id for each object must be sent through a GET request so that we can get the appropriate data back for each object from the server. This has to be done on a per object basis; I can't bundle them into one request because there is no API endpoint for that.
Therefore the objects are all stored in an array, so I've been trying to loop through the array and call the provider for each one. Note that the provider is returning an observable.
Since the provider is an asynchronous function the promise will resolve before the loop is finished, unless I time out the promise resolution. This defeats the whole point of the promise.
What is the correct way that I should go about doing this so that the looping provider calls are done before the promise resolves?
If I have an inner promise to resolve when the looping is done, won't it also resolve prematurely?
I also read that it is bad to have a bunch of observables open. Should I instead return each observable as a promise using toPromise()?
Here is the code to build the data:
asyncBuildData() {
var promise = new Promise((resolve, reject) => {
let completedRequests = 0;
for (let i = 0; i < 10; i++) {
this.provider.getStuffById(listOfStuff[i].id).subscribe(val => {
list.push(val)
completedRequests++;
})
}
console.log('cp', completedRequests); // completedRequests = 0
setTimeout(() => {
console.log('cp', completedRequests); // completedRequests = 10
let done = true;
if (done) {
resolve('Done');
} else {
reject('Not done');
}
}, 1500)
})
return promise;
}
Code from Provider:
getStuffById(stuffId) {
let url = url + stuffId;
return this.http.get(url)
.map(res => res.json());
}
Even though you can't bundle them into one request, you can still bundle them into one observable, of which those requests are fired in parallel, using .forkJoin():
buildData$() {
let parallelRequests = listOfStuffs.map(stuff => this.provider.getStuffById(stuff.id));
return Observable.forkJoin([...parallelRequests]);
}
and then in your component, you can just call:
buildData$.subscribe(val=>{
//val is an array of all the results of this.provider.getStuffById()
list =val;
})
Note that Obersvable.forkJoin() will for all requests to complete before emitting any values.
If I understand correctly then the following code should get you on your way. This will execute a promise, one at a time, for each element in the array.
var ids = [1,2,3,4,5];
ids.reduce(function (promise, id) {
return promise.then(function () {
let url = url + id;
return this.http.get(url)
.map(res => res.json());
});
}, Promise.resolve()).then(function(last) {
// handle last result
}, function(err) {
// handle errors
});
I tested this with a jQuery post and replaced it with yours from Ionic. If it fails then let me know.

How to Mock and test using an RxJS subject?

I have some functions that accept an RxJS subject (backed to a socket) that I want to test. I'd like to mock the subject in a very request reply fashion. Since I'm unsure of a clean Rx way to do this, I'm tempted to use an EventEmitter to form my fake socket.
Generally, I want to:
check that the message received on my "socket" matches expectations
respond to that message on the same subject: observer.next(resp)
I do need to be able to use data from the message to form the response as well.
The code being tested is
export function acquireKernelInfo(sock) {
// set up our JSON payload
const message = createMessage('kernel_info_request');
const obs = shell
.childOf(message)
.ofMessageType('kernel_info_reply')
.first()
.pluck('content', 'language_info')
.map(setLanguageInfo)
.publishReplay(1)
.refCount();
sock.next(message);
return obs;
}
You could manually create two subjects and "glue them together" as one Subject with Subject.create:
const sent = new Rx.Subject();
const received = new Rx.Subject();
const mockWebSocketSubject = Subject.create(sent, received)
const s1 = sent.subscribe(
(msg) => sentMsgs.push({ next: msg }),
(err) => sentMsgs.push({ error: err }),
() => sendMsgs.push({ complete: true })
);
const s2 = recieved.subscribe(
(msg) => sentMsgs.push({ next: msg }),
(err) => sentMsgs.push({ error: err }),
() => sendMsgs.push({ complete: true })
);
// to send a message
// (presumably whatever system you're injecting this into is doing the sending)
sent.next('weee');
// to mock a received message
received.next('blarg');
s1.unsubscribe();
s2.unsubscribe();
That said, it's really a matter of what you're testing, how it's structured, and what the API is.
Ideally you'd be able to run your whole test synchronously. If you can't for some Rx-related reason, you should look into the TestScheduler, which has facilities to run tests in virtualized time.

Resources