Read values from one observable but switch to another once it (the other one) emits - rxjs

My desired behaviour:
Run HTTP request
Immediately look up data in async cache
If cache has the value before HTTP emits - use cache value.
Use HTTP value after it's finally here.
If HTTP responds faster than cache - ignore cache.
So basically I would like to kick off two async processes, one of which is supposed to provide a value quickly but if it doesn't - I want to only use the value from a slower observable which takes precedence anyway.

To expand from my comments: the question is to trigger two observables in parallel and utilize the first emission even if the other observable hasn't emitted yet.
Normally you could use the merge function for it.
However you have a condition ("If HTTP responds faster than cache - ignore cache.") that is not natively fulfilled by the merge function nor by any standard RxJS operators.
But it is easy to write custom operators in RxJS from existing operators. For your case you could customize the filter operator to suit your needs. See here for a brief intro on how to write a custom operator.
export const filterLateCache = () => {
let serverEmitted = false;
return <T>(source: Observable<T>) => {
return source.pipe(
filter((data: any) => {
if (!!data.server) {
serverEmitted = true;
return true;
} else if (!!data.cache) {
if (serverEmitted) {
return false;
} else {
return true;
}
} else {
return false;
}
})
);
};
};
As you can see the boolean flags server and cache in the incoming notification are checked to decide whether the value must be emitted. So you'd need to append the values from the observables with these flags using the map operator.
merge(
server$.pipe(
map((value) => ({
server: true,
value: value,
}))
),
cache$.pipe(
map((value) => ({
cache: true,
value: value,
}))
)
)
.pipe(filterLateCache())
.subscribe({
next: ({ value }) => { // <-- utilize destructuring to ignore boolean flags
// handle response here
},
error: (error: any) => {
// handle errors
}
});
Working example: Stackblitz

Maybe it is worth looking at the raceWith: https://rxjs-dev.firebaseapp.com/api/operators/raceWith
Basically it would look like:
server$.pipe(raceWith(cache$)).subscribe(/*side effect that must be done*/);
The thing missing is that it does not fulfill requirement 4.

Related

Why is a stopped BehaviorSubject halting execution in a pipe of RXJS?

I have the following code:
this.workingStore$.pipe(
filter((workingStores) => !!workingStores[docID]),
concatMap((workingStores) => {
console.log(
'returning from concatMap',
workingStores[docID].getInitialDataSet(),
);
return workingStores[docID].getInitialDataSet();
}),
filter((isSet) => {
console.log('looking for set', isSet);
return isSet;
}),
),
workingStores[docID].getInitialDataSet() returns an Observable. Because the pipes that set it to true complete, the BehaviorSubject gets isStopped: true internally. Once it becomes true, the filter no longer fires for isSet.
Shouldn't it just know to return the final value? It seems that's not the case so how would I wrote this so the last filter always runs? If I do the following, it works, but is awfully code smelly
concatMap((workingStores) => {
if (
workingStores[docID].getInitialDataSet().getValue() === true
) {
return of(true);
}
return workingStores[docID].getInitialDataSet();
}),
I am aware ReplaySubject will give values, even after stopped, but I don't want to emit old values to any subscriber.
ReplaySubject has a constructor that accepts the number of latest events to replay. If you provide 1 it will act similarly to your BehaviorSubject.

How can i execute asynchronous code when an RxJS observable complete?

I would like to execute code when the observable complete. In my code, i execute this:
compact(): Observable<FileManifest> {
return this.loadIndex().pipe(
mergeMap((index) => index.walk()),
map((entry) => entry.manifest),
notUndefined(),
writeAllMessages(this.newPath, ProtoFileManifest),
finalize(async () => {
await Promise.all([
promises.rm(this.journalPath, { force: true }),
promises.rm(this.manifestPath, { force: true }),
]);
await promises.rename(this.newPath, this.manifestPath);
}),
);
}
The problem is that the finalize method is made for synchronous code. When i execute asynchronous code like above, the code will be executed independently from the subscribe.
I would like this will be execute when disposing resource of the observable but i want that when i subscribe, i always receive the event.
How can i put asynchronous code in the finalize method ?
Thanks
Ulrich
One way to do it is to create three observables instead of trying to do it all
in one. Each will make up a link in the sequential async chain you want to
make.
In order for the side effects in the promise-based observables to be lazy, we use defer.
Note that the defer callback's return value can be an observable, or an
"ObservableInput", which is what RxJS calls values it knows how to turn
into observables. This value can be (among other things) a promise.
({
compact(): Observable<FileManifest> {
const writeToTempManifest$ = this.loadIndex().pipe(
mergeMap((index) => index.walk()),
map((entry) => entry.manifest),
notUndefined(),
writeAllMessages(this.newPath, ProtoFileManifest)
);
const removeOldManifest$ = defer(() =>
Promise.all([
promises.rm(this.journalPath, { force: true }),
promises.rm(this.manifestPath, { force: true }),
])
);
const renameNewManifest$ = defer(() =>
promises.rename(this.newPath, this.manifestPath)
);
return from([
writeToTempManifest$,
removeOldManifest$,
renameNewManifest$,
]).pipe(concatAll());
},
});
Note that each of these observables potentially emits something (though I'm not familiar with the API). The first emits whatever the writeAllMessages operator does, while the second and third emit the resolved values of their respective promises. In the case of the second one, that's a two element array from the Promise.all.
If you want to suppress an observable's emitted values while still keeping it open until it completes, you can create an operator that does just that:
const silence = pipe(concatMapTo(EMPTY));

Why distinctUntilChanged won't work in asyncValidators in angular

When I press a key on a form input for some reason the async validator doesn't detect the distinctUntilChanged and it still sends API requests.
for example if I press 35, delete 5 and after that add 5 again it still sends the request.
this is the code:
(I've tried pretty much everything but still doesn't work)
validateSomeNumber(control: FormControl): Observable<any> | Promise <any> {
this.isSubmitBtnDisabled = true;
return control.valueChanges.pipe(
debounceTime(1000),
distinctUntilChanged(),
switchMap((value) => {
return this.apiService.someApiRequest({ 'to_number': control.value }).pipe(
map(res => {
console.log(res);
if (res.success) {
// console.log(res);
this.isSubmitBtnDisabled = false;
return null;
} else {
// console.log(res);
this.isSubmitBtnDisabled = true;
return{ 'invalidCharacters': true };
}
}),
);
}),
first()
);
}
By default validateSomeNumber is called after every value change.
If you return this on every value change
return control.valueChanges.pipe(
debounceTime(1000),
distinctUntilChanged(),
...
)
you're creating a new Observable of value changes on every value change. e.g if you type four characters you end up with four independent Observables each emitting one character and not with one Observable emitting four times. So debounceTime and distinctUntilChanged will only effecting the Observable you create on a particular value change but not the value change process as a whole. If they only effect an Observable that emits once they obviously don't work as you intend them to do.
You should return the http request directly
validateSomeNumber(control: FormControl): Observable<any> | Promise <any> {
this.isSubmitBtnDisabled = true;
return this.apiService.someApiRequest({ 'to_number': control.value }).pipe(
map(..),
);
}
Limiting the request frequency
Option 1: updateOn
To prevent the http request from being executed on every value change Angular recommends changing the updateOn property to submit or blur.
With template-driven forms:
<input [(ngModel)]="name" [ngModelOptions]="{updateOn: 'blur'}">
With reactive forms:
new FormControl('', {updateOn: 'blur'});
{updateOn: 'blur'} will only execute the validators when your input looses focus.
Option 2: Emulate debounceTime and distinctUntilChanged
Angular automatically unsubscribes from the previous Observable returned by the AsyncValidator if the form value changes. This allows you to emulate debounceTime with timer. To emulate distinctUntilChanged you can keep track of the last request term and do the equality check yourself.
private lastRequestTerm = null;
validateSomeNumber(control: FormControl): Observable<any> | Promise <any> {
this.isSubmitBtnDisabled = true;
// emulate debounceTime
return timer(1000).pipe(
// emulate distinceUntilChanged
filter(_ => control.value != this.lastRequestTerm),
switchMap(() => {
this.lastSearchTerm = control.value;
return this.apiService.someApiRequest({ 'to_number': control.value });
}),
map(..)
);
}

Angular 6 unit test rxjs 6 operator tap unit test interceptor

Since I update my code to the new Rxjs 6, I had to change the interceptor code like this:
auth.interceptor.ts:
...
return next.handle(req).pipe(
tap((event: HttpEvent<any>) => {
if (event instanceof HttpResponse) {
// do stuff with response if you want
}
}),
catchError((error: any) => {
if (error instanceof HttpErrorResponse) {
if (error.status === 401) {
this.authService.loginRedirect();
}
return observableThrowError(this.handleError(error));
}
})
);
and I'm not able to test the rxjs operators "tap" and "catchError".
Actually i'm only able to test if pipe is called:
it('should intercept and handle request', () => {
const req: any = {
clone: jasmine.createSpy('clone')
};
const next: any = {
handle: () => next,
pipe: () => next
};
spyOn(next, 'handle').and.callThrough();
spyOn(next, 'pipe').and.callThrough();
interceptor.intercept(req, next);
expect(next.handle).toHaveBeenCalled();
expect(next.pipe).toHaveBeenCalled();
expect(req.clone).toHaveBeenCalled();
});
Any help is apreciated on how to spy the rxjs operators
I think the problem is that you shouldn't be testing that operators were called like this at the first place.
Operators in both RxJS 5 and RxJS 6 are just functions that only "make recipe" how the chain is constructed. This means that checking if tap or catchError were called doesn't tell you anything about it's functionality or whether the chain works at all (it might throw an exception on any value and you won't be able to test it).
Since you're using RxJS 6 you should rather test the chain by sending values through. This is well documented and pretty easy to do https://github.com/ReactiveX/rxjs/blob/master/doc/marble-testing.md
In your case you could do something like this:
const testScheduler = new TestScheduler((actual, expected) => {
// some how assert the two objects are equal
// e.g. with chai `expect(actual).deep.equal(expected)`
});
// This test will actually run *synchronously*
testScheduler.run(({ cold }) => {
const next = {
handle: () => cold('-a-b-c--------|'),
};
const output = interceptor.intercept(null, next);
const expected = ' ----------c---|'; // or whatever your interceptor does
expectObservable(output).toBe(expected);
});
I think you'll get the point what this does...

Chained redux-observable epic only fires correctly once

I've set up an epic that waits for another epic to complete, much like #jayphelps' answer here: Invoking epics from within other epics
However I've found that it only seems to run once. After that I can see the CART_CONFIG_READY action in the console but the DO_THE_NEXT_THING action is not triggered.
I've tried various combinations of mergeMap and switchMap, with and without take but nothing seems to help.
This is (kind of) what my code looks like.
import { NgRedux } from '#angular-redux/store';
import { Observable } from 'rxjs/Observable';
import { ActionsObservable } from 'redux-observable';
export class CartEpicsService {
checkCart = (action$: ActionsObservable<any>, store: NgRedux<any>) => {
return action$.ofType('CHECK_CART')
.switchMap(() => {
console.log('___LISTENING___');
return action$.ofType('CART_CONFIG_READY')
.take(1) // removing this doesn't help
.mergeMap(() => {
console.log('___RECEIVED___');
// do stuff here
return Observable.of({
type: 'DO_THE_NEXT_THING'
});
})
.startWith({
type: 'GET_CART_CONFIG'
});
});
}
getCartConfig = (action$: ActionsObservable<any>, store: NgRedux<any>) => {
return action$.ofType('GET_CART_CONFIG')
.switchMap(() => {
const config = store.getState().config;
// we already have the config
if (config) {
return Observable.of({
type: 'CART_CONFIG_READY'
});
}
// otherwise load it from the server using out HTTP service
return this.http.get('/cart/config')
.switchMap((response) => {
return Observable.concat(
Observable.of({
type: 'CART_CONFIG_SUCCESS'
}),
Observable.of({
type: 'CART_CONFIG_READY'
})
);
})
.catch(error => Observable.of({
type: 'CART_CONFIG_ERROR',
error
}));
});
}
}
For context I need the response from the /cart/config endpoint to check the validity of the cart. I only need to download the config once.
Here is a runnable example on JS Bin:
https://jsbin.com/vovejibuwi/1/edit?js,console
Dang this is definitely a tricky one!
Cause
When state.config === true you return an Observable of CART_CONFIG_READY that emits synchronously, whereas during the first time the http request (or delay, in the jsbin) means it is always going to be async.
Why this makes a difference is in the checkCart epic you return an observable chain that listens for CART_CONFIG_READY with action$.ofType('CART_CONFIG_READY') but also applies a .startWith({ type: 'GET_CART_CONFIG' }). That means that GET_CART_CONFIG is going to be emitted synconously before action$.ofType('CART_CONFIG_READY') is subscribed because startWith is basically shorthand for a concat, which might would make the issue more clear if you're familiar with it. It's nearly exactly the same as doing this:
Observable.concat(
Observable.of({
type: 'GET_CART_CONFIG'
}),
action$.ofType('CART_CONFIG_READY') // not subscribed until prior complete()s
.take(1)
.mergeMap(() => {
// stuff
})
);
So to summarize, what is happening the second time around GET_CART_CONFIG is dispatched synchronously, getCartConfig receives it and sees the config is already in the store so it synchronously dispatches CART_CONFIG_READY. But we are not yet listening for it in checkCart so it goes unanswered. Then that callstack returns and the next Observable in the concat, our action$.ofType('CART_CONFIG_READY') chain, gets subscribed to. But too late, the action it listens for has already been emitted!
Solutions
One way to fix this is to make either the emitting of CART_CONFIG_READY always async, or to start listening for it in the other epic before we dispatch GET_CART_CONFIG.
1. emit CART_CONFIG_READY async
Observable.of accepts a scheduler as its last argument, and RxJS supports several of them.
In this case you could use the AsyncScheduler (macrotask) or the AsapScheduler (microtask). Both will work in this case, but they schedule on different times in the JavaScript event loop. If you're not familiar with event loop tasks, check this out.
I would personally recommend using the AsyncSheduler in this case because it will provide the closest async behavior to making an http request.
import { async } from 'rxjs/scheduler/async';
// later inside your epic...
return Observable.of({
type: 'CART_CONFIG_READY'
}, async);
2. Listen for CART_CONFIG_READY before emitting GET_CART_CONFIG
Because startWith is shorthand for a concat (which we don't want to do) we instead need to use some form of merge, with our ofType chain first so that we listen before emitting.
action$.ofType('CART_CONFIG_READY')
.take(1)
.mergeMap(() => {
// stuff
})
.merge(
Observable.of({ type: 'GET_CART_CONFIG' })
)
// or
Observable.merge(
action$.ofType('CART_CONFIG_READY')
.take(1)
.mergeMap(() => {
// stuff
}),
Observable.of({ type: 'GET_CART_CONFIG' })
)
// both are exactly the same, pick personal preference on appearance
You only need to do one of these solutions, but it wouldn't hurt to do both of them. Offhand I would probably recommend using both just so that things are consistent and expected, even if they are a bit more verbose.
You might also be happy to know that Observable.of accepts any number of items, which will be emitted in order. So you don't need to use concat:
// before
Observable.concat(
Observable.of({
type: 'CART_CONFIG_SUCCESS'
}),
Observable.of({
type: 'CART_CONFIG_READY'
})
)
// after
Observable.of({
type: 'CART_CONFIG_SUCCESS'
}, {
type: 'CART_CONFIG_READY'
})
Thanks so much for the jsbin btw, it made it much easier to debug.
Edit based on your comment:
Out of curiosity did you figure this out through experience or debugging?
A combination of both. I've dealt with a ton of async/scheduled code and ordering is very commonly the source of issues. I scanned the code, mentally picturing execution, noticed the difference in async vs sync depending on codepath, then I made a quick operator to make it easy for me to confirm the order in which any Observable chain is subscribed to.
Observable.prototype.logOnSubscribe = function (msg) {
// defer is a pretty useful Observable to learn if you haven't yet
return Observable.defer(() => {
console.log(msg);
return this; // the original source
});
};
I applied it to several places, but the most important are these two:
action$.ofType('CART_CONFIG_READY')
.take(1)
.mergeMap(() => {
// stuff
})
.logOnSubscribe('listening for CART_CONFIG_READY') // <--- here
.startWith({
type: 'GET_CART_CONFIG'
});
// and in the other epic...
if (hasConfig) {
return Observable.of({
type: 'CART_CONFIG_READY'
})
.logOnSubscribe('emitting CART_CONFIG_READY'); // <--- and here
}
It confirmed that in the second code path CART_CONFIG_READY was getting emitted before the other epic was listening for it.

Resources