I'm trying to combine latest values from 3 different streams ignoring that one(or more) of them that hasn't emit any value yet.
const myStream1$:Observable<>=// steam from somewhere,
myStream2$:Observable<string>=// steam from somewhere,
myStream3$:Observable<string>=// steam from somewhere;
Observable.combineLatest(myStream1$, myStream2$, myStream3$)
.do(([valFromStream1, valFromStream2, valFromStream3])=>
{
// I want to do some side-effect operation even if one(or all) of those 3 streams hasn't emit any value yet
})
.takeUntil(terminator$)
.subscribe();
How to achieve that?
I can achieve that using next logic(but it is as ugly as sh*t), I guess there should be an operator that does that auto-magically:
const myStream1$:Observable<string>=// steam from somewhere,
myStream2$:Observable<string>=// steam from somewhere,
myStream3$:Observable<string>=// steam from somewhere;
const subj1 = new BehaviorSubject<string>(undefined),
subj2 = new BehaviorSubject<string>(undefined),
subj3 = new BehaviorSubject<string>(undefined);
myStream1$
.do((val)=>{
subj1.next(val);
}).subscribe();
myStream2$
.do((val)=>{
subj2.next(val);
}).subscribe();
myStream3$
.do((val)=>{
subj3.next(val);
}).subscribe();
Observable.combineLatest(subj1, subj2, subj3)
.do(([valFromStream1, valFromStream2, valFromStream3])=>
{
// I want to do some side-effect operation even if one of those 3 streams hasn't emit any value yet
})
.takeUntil(terminator$)
.subscribe();
You can enforce combineLatest to emit by start each stream with a null value, then you can perform you logic conditionally base on whether the value is null
combineLatest(myStream1$.startWith(null),
myStream2$.startWith(null),
myStream3$.startWith(null))
.do(([v1,v2,v3])=>{
....
}).takeUntil(terminator$)
Related
I use the following code in an angular app. I used the RxJS map call similar to how array map is used. After reading about RxJS switchmap operator, I'm not sure whether I should use map or switchmap. Should I use switchmap so the observable which is returned from the http call is closed so there is no memory leak?
getPeopleForTypeahead(term: string): Observable<IPersonForTypeahead[]> {
var peopleUrl = `https://localhost:5001/api/peoplesearch?name=${term}`;
return this.http.get<any>(peopleUrl)
.pipe(
map(pl => {
return this.peopleAsFlattened(pl.peopleList).reduce((p, c) => p.concat(c));
}),
catchError(this.handleError('getPeopleForTypeahead', []))
);
}
peopleAsFlattened = (pla: IPeopleList[]) => {
return pla.map(pl => pl.people.map(p => {
return {
id: p.id,
fullName: p.fullNames[0].firstName + " " + p.fullNames[0].lastName
};
}));
}
map and switchMap have completely different purposes:
map - transform the shape of an emission
switchMap - subscribe to an observable and emit its emissions into the stream
map
Use map when you want transform the shape of each emission. Ex: emit the user name property, instead of the entire user object:
userName$: Observable<string> = this.service.getUser(123).pipe(
map(user => user.name)
);
switchMap
Use switchMap when you want to map an emission to another observable and emit its emissions. Ex: You have an observable of some id and want to emit the resource after fetching it:
user$: Observable<User> = this.userId$.pipe(
switchMap(id => this.service.getUser(id)),
);
When user$ is subscribed to, the user returned from service.getUser(id) is emitted (not the userId string).
switchMap is not interchangeable with the map operator, nor vise versa. Although both of them has to do with mapping (as their names suggest), they have two separate use-cases.
In your particular case, the map operator is the way to go.
When to use switchMap?
You can only use switchMap(cb) when you check all these requirements:
Your callback function, cb, passed into switchMap returns an observable, observable$.
If your cb (callback function) does not return an observable, you should look into operators that don't handle higher-level observables, such as filter and map (what you actually needed); not operators that handle higher-level observables such as concatMap and well, switchMap.
You want to execute your cb sequentially before the next operation down the pipeline (after switchMap) executes.
Maybe you want to run logic inside of cb, and optionally get the return value of cb after executing, so that you can pass it down the pipeline for further processing, for example.
When you want to "discard" what will happen to cb's execution and re-execute cb every time the source observable (the thing that trickles down to switchMap(cb)) emits a new value/notification.
Applying what we hopefully learned, we know that your cb:
pl => {
return this.peopleAsFlattened(pl.peopleList).reduce((p, c) => p.concat(c));
}
returns a plain JavaScript array; not an observable. This takes using switchMap out of the question since it violates the first requirement I made up above.
Hopefully that makes sense. :)
We use switchMap when the source observable is a hot observable. In which case you prefer the behaviour that cancel the succeeding observable when source emits.
In your code, you source is a one-off http call which means it will not emit multiple times and the follow up action is not executing observable but to mutate an array. There is no need to use switchMap
I have tried to unsubscribe within the subscribe method. It seems like it works, I haven't found an example on the internet that you can do it this way.
I know that there are many other possibilities to unsubscribe the method or to limit it with pipes. Please do not suggest any other solution, but answer why you shouldn't do that or is it a possible way ?
example:
let localSubscription = someObservable.subscribe(result => {
this.result = result;
if (localSubscription && someStatement) {
localSubscription.unsubscribe();
}
});
The problem
Sometimes the pattern you used above will work and sometimes it won't. Here are two examples, you can try to run them yourself. One will throw an error and the other will not.
const subscription = of(1,2,3,4,5).pipe(
tap(console.log)
).subscribe(v => {
if(v === 4) subscription.unsubscribe();
});
The output:
1
2
3
4
Error: Cannot access 'subscription' before initialization
Something similar:
const subscription = of(1,2,3,4,5).pipe(
tap(console.log),
delay(0)
).subscribe(v => {
if (v === 4) subscription.unsubscribe();
});
The output:
1
2
3
4
This time you don't get an error, but you also unsubscribed before the 5 was emitted from the source observable of(1,2,3,4,5)
Hidden Constraints
If you're familiar with Schedulers in RxJS, you might immediately be able to spot the extra hidden information that allows one example to work while the other doesn't.
delay (Even a delay of 0 milliseconds) returns an Observable that uses an asynchronous scheduler. This means, in effect, that the current block of code will finish execution before the delayed observable has a chance to emit.
This guarantees that in a single-threaded environment (like the Javascript runtime found in browsers currently) your subscription has been initialized.
The Solutions
1. Keep a fragile codebase
One possible solution is to just ignore common wisdom and continue to use this pattern for unsubscribing. To do so, you and anyone on your team that might use your code for reference or might someday need to maintain your code must take on the extra cognitive load of remembering which observable use the correct scheduler.
Changing how an observable transforms data in one part of your application may cause unexpected errors in every part of the application that relies on this data being supplied by an asynchronous scheduler.
For example: code that runs fine when querying a server may break when synchronously returned a cashed result. What seems like an optimization, now wreaks havoc in your codebase. When this sort of error appears, the source can be rather difficult to track down.
Finally, if ever browsers (or you're running code in Node.js) start to support multi-threaded environments, your code will either have to make do without that enhancement or be re-written.
2. Making "unsubscribe inside subscription callback" a safe pattern
Idiomatic RxJS code tries to be schedular agnostic wherever possible.
Here is how you might use the pattern above without worrying about which scheduler an observable is using. This is effectively scheduler agnostic, though it likely complicates a rather simple task much more than it needs to.
const stream = publish()(of(1,2,3,4,5));
const subscription = stream.pipe(
tap(console.log)
).subscribe(x => {
if(x === 4) subscription.unsubscribe();
});
stream.connect();
This lets you use a "unsubscribe inside a subscription" pattern safely. This will always work regardless of the scheduler and would continue to work if (for example) you put your code in a multi-threaded environment (The delay example above may break, but this will not).
3. RxJS Operators
The best solutions will be those that use operators that handle subscription/unsubscription on your behalf. They require no extra cognitive load in the best circumstances and manage to contain/manage errors relatively well (less spooky action at a distance) in the more exotic circumstances.
Most higher-order operators do this (concat, merge, concatMap, switchMap, mergeMap, ect). Other operators like take, takeUntil, takeWhile, ect let you use a more declarative style to manage subscriptions.
Where possible, these are preferable as they're all less likely to cause strange errors or confusion within a team that is using them.
The examples above re-written:
of(1,2,3,4,5).pipe(
tap(console.log)
first(v => v === 4)
).subscribe();
It's working method, but RxJS mainly recommend use async pipe in Angular. That's the perfect solution. In your example you assign result to the object property and that's not a good practice.
If you use your variable in the template, then just use async pipe. If you don't, just make it observable in that way:
private readonly result$ = someObservable.pipe(/...get exactly what you need here.../)
And then you can use your result$ in cases when you need it: in other observable or template.
Also you can use pipe(take(1)) or pipe(first()) for unsubscribing. There are also some other pipe methods allowing you unsubscribe without additional code.
There are various ways of unsubscribing data:
Method 1: Unsubscribe after subscription; (Not preferred)
let localSubscription = someObservable.subscribe(result => {
this.result = result;
}).unsubscribe();
---------------------
Method 2: If you want only first one or 2 values, use take operator or first operator
a) let localSubscription =
someObservable.pipe(take(1)).subscribe(result => {
this.result = result;
});
b) let localSubscription =
someObservable.pipe(first()).subscribe(result => {
this.result = result;
});
---------------------
Method 3: Use Subscription and unsubscribe in your ngOnDestroy();
let localSubscription =
someObservable.subscribe(result => {
this.result = result;
});
ngOnDestroy() { this.localSubscription.unsubscribe() }
----------------------
Method 4: Use Subject and takeUntil Operator and destroy in ngOnDestroy
let destroySubject: Subject<any> = new Subject();
let localSubscription =
someObservable.pipe(takeUntil(this.destroySubject)).subscribe(result => {
this.result = result;
});
ngOnDestroy() {
this.destroySubject.next();
this.destroySubject.complete();
}
I would personally prefer method 4, because you can use the same destroy subject for multiple subscriptions if you have in a single page.
What's the best way to handle asynchronous updates in the middle of an Observable stream.
Let's say there are 3 observables:
Obs1 (gets data from API) -> pipes to Obs2
Obs2 (transforms data) -> pipes to Obs3
Obs3 (sends transformed data)
(The actual application is more complex, and there's reasons it's not done in a single Observable, this is just a simple example).
That all works well and good if it's a linear synchronous path.
But we also have async messages that will change the output of Obs2.
3 scenarios I'm asking about are:
- we fetch data, and go through Obs1, Obs2 & Obs3
- we get a message to make a change, go through Obs2 & Obs3
- we get a different message to make a change which also needs to apply the change from the previous message, through Obs2 & Obs3
The main problem here is that there are different types of asynchronous messages that will change the outcome of Obs2, but they all need to still know what the previous outcome of Obs2 was (so the any other changes from messages that happened before is still applied)
I have tried using switchMap in Obs2 with a scan in Obs1 like this:
obs1
const obs1$ = obs1$.pipe(
// this returns a function used in the reducer.
map((data) => (prevData) => 'modifiedData',
scan((data, reducer) => reducer(betsMap), {})
)
obs2
const obs2$ = obs1$.pipe(
switchMap(data =>
someChange$.pipe(map(reducer => reducer(data)))
)
)
where someChange$ is a BehaviorSubject applying a change using another reducer function.
This works fine for async message #1 that makes some change.
But when message #2 comes in and a different change is needed, the first change is lost.
the changes that should be in "prevData" in obs1$ is always undefined because it happens before the message is applied.
How can I take the output from obs2$ and apply asynchronous updates to it that remembers what all of the past updates was? (in a way where I can clear all changes if needed)
So if i got the question right, there are two problems that this question tackles:
First: How to cache the last 2 emitted values from stream.
scan definitely is the right way, if this cache logic is needed in more than one place/file, I would go for a custom pipe operator, like the following one
function cachePipe() {
return sourceObservable =>
sourceObservable.pipe(
scan((acc, cur) => {
return acc.length === 2 ? [...acc.slice(1), cur] : [...acc, cur];
}, [])
);
}
cachePipe will always return the latest 2 values passed trough the stream.
...
.pipe(
cachePipe()
)
Second: How to access data from multiple streams at the same time, upon stream event
Here rxjs's combineLatest creation operator might do the trick for you,
combineLatest(API$, async1$ ,async2$,async3$)
.pipe(
// here I have access to an array of the last emitted value of all streams
// and the data can be passed to `Obs2` in your case
)
In the pipe I can chain whatever number of observables, which resolves the second problem.
Note:
combineLatest needs for all streams, inside of it, to emit once, before the operator strats to emit their combined value, one workaround is to use startWith operator with your input streams, another way to do it is by passing the data trough BehaviorSubject-s.
Here is a demo at CodeSandbox , that uses the cachePipe() and startWith strategy to combine the source (Obs1) with the async observables that will change the data.
With the pausable operator not implemented in RxJS v5, is there a better way to create a pausable interval? The code below works, but does so by keeping track of the last emitted value as an offset. It seems like there should be a better way...
const source = Rx.Observable.interval(100).share()
const offset = new Rx.BehaviorSubject(0)
let subscription;
let currentValue;
function start() {
subscription = source
.subscribe(i => {
currentValue = i + offset.value
})
}
function pause() {
source.take(1).subscribe(i => offset.next(i + offset.value))
subscription.unsubscribe()
}
The share() operator is an alias for .publish().refCount(). The refCount() means that the observable will clean itself up when no other subscribers to it exist. Because you're unsubscribing from source, it will clean itself up, then restart when subscribed to again. Use publish() with connect() instead. Here's the code:
const source = Observable.interval(100).publish();
source.connect();
// Start with false, change to true after 200ms, then false again
// after another 200ms
const pauser = Observable.timer(200)
.mapTo(true)
.concat(Observable.timer(200).mapTo(false))
.startWith(false);
const pausable = pauser
.switchMap(paused => (paused ? Observable.never() : source))
.take(10);
pausable.subscribe(x => console.log(x));
See this jsbin for a running example: http://jsbin.com/jomusiy/3/edit?js,console.
There's no general purpose way to do this. It depends on what exactly you mean by pausing and also what you're pausing. (Do you want to just stop emitting and then start again, do you buffer and drain, or do you effectively need to add a delay to the upstream, but otherwise preserve the distribution of upstream values in time?)
I have a way to do this in an efficient manner when the upstream is specifically a timer, as it is in your example. It is the answer to my own question here.
RxJS (5.0rc4): Pause and resume an interval timer
This one has the great advantage of preserving the source's value distribution in time, but just adding a delay to it.
For more general cases:
For cold observables: switch between a never and upstream. On pause, unsubscribe from upstream. skip ones that you've seen, and then switch to the skiped stream. While unpaused you must maintain a count of how many values you've emitted, so that you can skip that many the next time someone unpauses. You only have to remember how many you've seen before. However, every unpause causes the cold observable to replay from the beginning. This could be very inefficient in the general case. The code would look something like this. pauser here would be a Subject that you can set to true or false to pause upstream.
function pausableCold(pauser, upstream) {
var seen = 0;
return pauser.switch(paused => {
if (paused) {
return Observable.never();
}
else {
return upstream.skip(seen).do(() => seen++);
}
});
}
For hot or cold observables, you can use buffering. buffer while paused and then drain and concat to the hot upstream when unpaused. (This preserves all the values but but it doesn't preserve their distribution in time. Also, you should hot upstream with publish if it could be cold.)
The most efficient way is not really part of the Rx. What you really want is to tell the source to stop emitting and then start again. The way you do that is very specific to what the source is and how the source is generating values.
I'm trying to understand how flatMap works. I understand that it is a way for handling Observable< Observable < T>>.
Anyhow, I was testing the behaviour of it and got stuck with this:
let plusDom = document.querySelector('#plus');
let minusDom = document.querySelector('#minus');
let minusSource = Rx
.Observable
.fromEvent(minusDom, 'click');
let plusSource = Rx.Observable
.fromEvent(plusDom, 'click');
plusSource
.flatMap(function(c){
console.log('Flatmap called');
return minusSource;
})
.subscribe(function(e){
console.log('done');
})
Here it is the jsbin: https://jsbin.com/sefoyowuqe/edit?html,js,console,output
I don't understand this behaviour:
3 clicks on plusDom prints:
Flatmap called
Flatmap called
Flatmap called
1 click on minusDom prints:
done
done
done
Why when clicking the minusDom it replays the events as many times as we have click the plusDom?
flatMap basically places the returned stream flat into the original stream. What you are probably looking for is switchMap, which will switch to that returned stream and switch to a new stream when the original source emits data by discarding the old one.
When in doubt, switchMap is usually the safest alternative to use.
See the marble-diagrams for comparison:
Flatmap doesn't remove previously "flattened" streams and :
Switchmap removes previously "switched" streams.