rxjs combineLatest not working as expected - rxjs

I am struggling with the 'combineLatest' operator...
I have a operator chain like so:
const observable = interval(1000).pipe(
map((x) => 'myAction'),
mergeMap((action)=>
combineLatest([from([1,2,3]),of(action)])
),
tap(result=>{
console.log('-');
console.log(JSON.stringify(result));
})
);
I would expect this output:
[1, 'myAction']
[2, 'myAction']
[3, 'myAction']
what i get is just one output:
[3, 'myAction']
How can I achieve to get the expected result?

As the name suggests, combine latest only combines the most recent emissions for the given streams. Since from([1,2,3]) is synchronous, (effectively emits all its values at once), you can get some hard to predict behavior. I haven't tested this, but you may be able to switch the order of the observable and it might work as expected (since of(action) gets subscribed to first).
How I would solve this case:
Since of(action) is just wrapping a single value, I wouldn't bother. Just map the value into your observable directly. That might look like this:
const observable = interval(1000).pipe(
map(x => 'myAction'),
mergeMap(action => of(1,2,3).pipe(
map(n => [n, action])
)),
tap(result=>{
console.log('-');
console.log(JSON.stringify(result));
})
);

Related

RXJS pipe chaining vs single pipe

I was wondering is there any difference between using a single myObservable.pipe(...) vs chaining myObservable.pipe(...).pipe(...).
Question is are those 2 examples identical? Is there a use case for pipe chaining?
Example with single pipe:
from([1, 2, 3])
.pipe(
delay(1000),
map((value: number) => value * 2),
map((value: number) => value * 3)
)
.subscribe((value) => {
console.log('result:', value);
});
Example with chaining pipes:
from([1, 2, 3])
.pipe(delay(1000))
.pipe(map((value: number) => value * 2))
.pipe(map((value: number) => value * 3))
.subscribe((value) => {
console.log('result:', value);
});
No, there isn't. However, I would like to help you understand why:
This code:
from([1,2,3]).pipe(
delay(1000)
)
is 100% equivalent to:
delay(1000)(
from([1, 2, 3])
)
And this code:
from([1, 2, 3])
.pipe(
delay(1000),
map((value: number) => value * 2),
)
is the same as:
map((value: number) => value * 2)(
delay(1000)(
from([1, 2, 3])
)
)
Etc, etc.
The thing is that piepable operators (the ones that you can use inside pipe) are "Observable enhancers" (a type of higher order functions). What those functions return is a function that will enhance the Observable that has received as an argument and it will return a new Observable with some "enahnced" behavior.
Therefore, pipe is just sugar for composing those Observable enhancers in a more declarative manner.
For instance, a basic implementation of the map piepable operator would look like this:
const map = <I, O>(mapper: (input: I) => O) =>
(source$: Observable<I>): Observable<O> =>
new Observable<O>(observer => source$.subscribe({
next: (value) => {
observer.next(mapper(value));
},
error: (e) => {
observer.error(e);
},
complete: () => {
observer.complete();
}
}))
RxJS is known for its operators. Essentially there is a kind of operators that take an observable as input and return another observabl, these are pipeable operators for example filter, map etc.
A Pipeable Operator is a function that takes an Observable as its input and returns another Observable. It is a pure operation: the previous Observable stays unmodified.
Now, the pipe operator just gives us syntax flexibility, to combine operators (logically thinking they are inside a pipe, where one would be run after the other).
Theoretically it doesn't make a difference that you chain pipe operators, but the sole purpose of pipe operator is to combine multiple operators, so its a good practice to combine group of operators inside a single pipe.

RxJS way to build the chain of consecutive api calls

If I deal with an arbitrary array which values have to be mapped to promises and it is crucial to wait for previous call completion before the next one performed, I usually follow this pattern:
const later = ms =>
new Promise(resolve =>
setTimeout(_ => resolve(console.log(ms)), ms));
console.log('chain start')
let chain = Promise.resolve();
[1, 2, 3, ...].forEach(val =>
chain = chain.then(later.bind(null, val * 100)))
chain.then(_ => console.log('chain end'))
The important parts here are that 1 - we don't know how many items array could carry and 2 - we can't use Promise.all (consecutive calls or queue required).
The question is:
What is the best RxJS way of doing so? So, observables instead of promises.
(I'm aware of .toPromise)
assume all your array contains [urls]
from([url1,url2,url3...]).pipe(concatMap(url=>defer(()=>fetch(url))))
.subscribe(results=console.log(results))

RxJS throttle same value but let new values through

"Here you have", someone says and you are given this input stream of values that you somewhat want to do distinctUntilChanged() upon...
Input: '1-1----11---2--1122----1---2---2-2-1-2---|'
Output: '1-----------2--1-2-----1---2-------1-2---|'
Nothing weird so far,
But now someone says "it's okey" if the same value comes again, "but only if it's not to soon!". I want at least '----' ticks between the same value. "Okey" you say and you add a throttle
const source = new Subject<number>();
// mysterious cave troll is randomly source.next(oneOrTwo)
const example = source.pipe(throttle(val => interval(4000)));
Input: '1-1----11---2--1122----1---2---2-2-1-2-----|'
Output: '1------1----2----2-----1-------2-----2-----|'
"That's not what I want! Look at all the value you missed", referring to that you throttle in regards to all values being streamed.
Input: '1-1----11---2--1122----1---2---2-2-1-2-----|'
Output: '1------1----2----2-----1-------2-----2-----|'
'-------------->1<--------->2<----->1<------|' <-- Missed values
"Here, let me show show you" the mysterious man says and gives you this
Wanted output
Input: '1-1----11---2--1112----1---2---2-2-1-2-----|'
Output: '1------1----2--1--2----1---2-----2-1-------|'
My answer to this is that it feels like a combined window wouldn't do.
From someone more experienced,
is this a hard problem to solve? (or have I missed an obvious solution)
First I came up with idea to somehow combine distinctUntilChanged() and throttleTimte(), however it was not possible for me to come up with solution and then I tried something else.
The operator I came up with is throttleDistinct() that works as you would like to: StackBlit Editor Link
It has 2 parameters which are:
duration: number which is in milliseconds and is similar to
duration in throttleTime(duration: number)
equals: (a: T, b: T) => boolean which is function to compare if previous item is equal to next item, which has default
implementation of (a, b) => a === b
import { of, fromEvent, interval, Observable } from 'rxjs';
import { map, scan, filter, } from 'rxjs/operators';
const source = fromEvent(document, 'keypress')
.pipe(map((x: any) => x.keyCode as number))
source
.pipe(
throttleDistinct(1000),
)
.subscribe((x) => console.log('__subscribe__', x));
export function throttleDistinct<T>(
duration: number,
equals: (a: T, b: T) => boolean = (a, b) => a === b
) {
return (source: Observable<T>) => {
return source
.pipe(
map((x) => {
const obj = { val: x, time: Date.now(), keep: true };
return obj;
}),
scan((acc, cur) => {
const diff = cur.time - acc.time;
const isSame = equals(acc.val, cur.val)
return diff > duration || (diff < duration && !isSame)
? { ...cur, keep: true }
: { ...acc, keep: false };
}),
filter((x) => x.keep),
map((x) => x.val),
)
}
}
Off the top of my head, you want to buffer by the time interval, then distinct within each buffer.
Effectively you want to restart / reboot the distinct run every n milliseconds.
source.pipe(
bufferTime(ms),
mergeMap(bufferArray => from(bufferArray).pipe(distinctUntilChanged()) )
)
This is my second attempt, it filters the stream by output (rather than taking distinctUntil) then throttles and merges the two streams.
Of course, we may not have a known set of values (1,2,...n).
If I can figure out that wrinkle, will add a further example.
const output = merge(
source.pipe( filter(x => x === 1), throttle(val => interval(ms))),
source.pipe( filter(x => x === 2), throttle(val => interval(ms)))
)
Here is my check (ms = 4000)
input 1-1----11---2--1112----1---2---2-2-1-2-----
expected 1------1----2--1--2----1---2-----2-1-------
filter(1) 1-1----11------111-----1-----------1-------
throttle(1) 1------1-------1-------1-----------1-------
filter(2) ------------2-----2--------2---2-2---2-----
throttle(2) ------------2-----2--------2-----2---------
merged 1------1----2--1--2----1---2-----2-1-------
expected 1------1----2--1--2----1---2-----2-1-------
Extending to n values
I think this will work where the set of values in the stream is not known in advance (or has a large range so extending the previous answer is impractical).
It should work as long as the source completes.
merge(
source.pipe(
distinct().pipe(
mapTo(distinctVal => source.pipe(
filter(val = val === distinctVal),
throttle(val => interval(ms))
)
)
)
)
I don't have a proof yet, will post that next.
Here is a tricky solution base on theory of operators, but I can't sure it really works, because I will need to mock a source emission first.
So throttle and distinct stream always have the latest value cached, zip make sure they always got emitted in pair, zip will always emit when any of the stream emit because it's shareReplay(1).
We always take the value emit from distinctStream, even when zip stream is trigger by throttle, because distinctStream always have the last cached value.
const throttleStream= source.pipe(throttle(val => interval(4000)),shareReplay(1))
const distinctStream= source.pipe(distinctUntilChanged(),shareReplay(1))
zip(throttleStream,distinctStream).pipe(
map((t,d)=>d)
)
I found a solution that works, does someone have any take on this?
source.pipe(
windowTime(4000),
concatMap(obs => obs.pipe(distinct()))
);
Examples from before, in a StackBlitz example
UPDATE: this does not actually work 100%. It only take the current window into consideration. So you can for example have
`[1-12][2---]` which would give `1--22---|`
where [----] would represent the time window. In other words, if a value is first emitted last in one window and emitted first in the next window, the same value will pass through right after each other.
Thanks #eric99 for making me realize this.

Using concat inside mergeMap

I have a hard time understanding something related to using a concat function inside a mergeMap operator.
The documentation for concat says that
You can pass either an array of Observables, or put them directly as arguments.
When I put the Observables directly as arguments, like in the following example, I correctly get 20 and 24 in the console.
of(4)
.pipe(
mergeMap(number => concat(of(5 * number), of(6 * number)))
)
.subscribe(value => console.log(value));
But when I put them as an array, then in the console I get the Observables and not their values:
of(4)
.pipe(
mergeMap(number => concat([of(5 * number), of(6 * number)]))
)
.subscribe(value => console.log(value));
Here's a live version in Stackblitz.
Any idea why is that? Shouldn't both examples work identically?
Those two scenarios are different and they should not work identically. concat takes Observables as arguments and it will sequentially subscribe to those streams and only subscribe to the next Observable when the previous one completed. Every operator or creation method returns an Observable. This means that in the first example, when you are using concat, it will return an Observable that emits 20 and then 24. Because you are dealing with a nested Observable you have to use mergeMap which will subscribe to the resulting Observable returned by concat.
Now in the second example, if you pass in an array, concat will convert this (using from() internally) to an Observable that emits 2 values, and those values are Observables again. So you have 3 levels of nesting here. The first is the most outer Observable, the source, which of(4), the second level is the one you map to inside your mergeMap and the third in the second example are the Observables inside your array. The thing is you only flatten the levels up to level 2 but not the 3rd level. Again, in your second example the Observable returned by mergeMap emits two Observables but those are just the proxies and not the values emitted by these Observables. If you want to subscribe to those as well, you could chain on another mergeMap like so
concatArray() {
of(4)
.pipe(
mergeMap(number => concat([of(5 * number), of(6 * number)])),
mergeMap(x => x)
)
.subscribe(value => console.log(value));
}
Another way is to spread the array so that concat does not receive an object that is ArrayLike but rather Observables directly:
concatArray() {
of(4)
.pipe(
mergeMap(number => concat(...[of(5 * number), of(6 * number)]))
)
.subscribe(value => console.log(value));
}
Both will print out:
20
24
I hope this makes this a little bit more clear and describes the differences between the first and the second example.

'of' vs 'from' operator

Is the only difference between Observable.of and Observable.from the arguments format? Like the Function.prototype.call and Function.prototype.apply?
Observable.of(1,2,3).subscribe(() => {})
Observable.from([1,2,3]).subscribe(() => {})
It is important to note the difference between of and from when passing an array-like structure (including strings):
Observable.of([1, 2, 3]).subscribe(x => console.log(x));
would print the whole array at once.
On the other hand,
Observable.from([1, 2, 3]).subscribe(x => console.log(x));
prints the elements 1 by 1.
For strings the behaviour is the same, but at character level.
Not quite. When passing an array to Observable.from, the only difference between it and Observable.of is the way the arguments are passed.
However, Observable.from will accept an argument that is
a subscribable object, a Promise, an Observable-like, an Array, an iterable or an array-like object to be converted
There is no similar behaviour for Observable.of - which always accepts only values and performs no conversion.
One line Difference :
let fruits = ['orange','apple','banana']
from : Emit the items one by one of array. For example
from(fruits).subscribe(console.log) // 'orange','apple','banana'
of : Emit the whole array at once. For example
of(fruits).subscribe(console.log) // ['orange','apple','banana']
NOTE: of operator can behave as from operator with spread operator
of(...fruits).subscribe(console.log) // 'orange','apple','banana'
Another interesting fact is Observable.of([]) will be an empty array when you subscribe to it.
Where as when you subscribe to Observable.from([]) you wont get any value.
This is important when you do a consecutive operation with switchmap.
Ex:
In the below example, I am saving a job and then sites, and then comments as a stream.
.do((data) => {
this.jobService.save$.next(this.job.id);
})
.switchMap(() => this.jobService.addSites(this.job.id, this.sites)
.flatMap((data) => {
if (data.length > 0) {
// get observables for saving
return Observable.forkJoin(jobSiteObservables);
} else {
**return Observable.of([]);**
}
})).do((result) => {
// ..
})
.switchMap(() => this.saveComments())
....
if there's no site to save, ie; data.length = 0 in addSite section, the above code is returning Observable.of([]) and then goes to save comments. But if you replace it with Observable.from([]), the succeeding methods will not get called.
rxfiddle
of will emit all values at once
from will emit all values one by one
of with spread operator = from operator
from: Create observable from array, promise or iterable. Takes only one value. For arrays, iterables and strings, all contained values will be emitted as a sequence
const values = [1, 2, 3];
from(values); // 1 ... 2 ... 3
of: Create observable with variable amounts of values, emit values in sequence, but arrays as single value
const values = [1, 2, 3];
of(values, 'hi', 4, 5); // [1, 2, 3] ... 'hi' ... 4 ... 5
from returns notification in chunks i.e. one by one.
for eg: from("abcde") will return a => b => c => d => e
of returns complete notification.
for eg: of("abcde") will return abcde.
https://stackblitz.com/edit/typescript-sckwsw?file=index.ts&devtoolsheight=100
The from operator takes source of events. from(source)
let array = [1,2,3,4,5]
from(array); //where array is source of events, array[of events]
let promise = new Promise(function(resolve, reject) {
// executor (the producing code, "singer")
});
from(promise); //where promise is source of event, promise(of event)
let observable = Observable.create(function(observer) {
observer.next(1);
observer.next(2);
observer.next(3);
observer.next(4);
observer.next(5);
observer.complete();
});
from(observable); // where obsservable is source of events.
The of operator takes intividual events. of(event1, event2, event3)
of(1,2,3,4,5); // where 1,2,3,4,5 are individual events
I found it easier to remember the difference when the analogy with .call / .apply methods came into my mind.
You can think of it this way:
normally, all arguments, that are passed separately (separated by comma), are also emitted separately, in the order they were passed. of() just emits all arguments one by one as they are (like .call method passes arguments to the function it was called on)
from() is like .apply in a sense that it can take an array of values as an argument, and convert array elements into separate arguments, separated by comma.
So, if you have an array and want each element to be emitted separately, you can use from() or get the same behavior by using of() with spread operator, like of(...arr).
It's bit more complicated then that (from can also take observables) but with this analogy it will probably be easier to remember the main difference.
Yes it is true that of will result in an output in single go and from will happen one at a time. But there is more difference related to number of arguments and type of arguments.
You can pass any number of arguments to the Of. Each argument emitted separately and one after the other. It sends the Complete signal in the end.
However you can send only one argument to the from operator and that one argument should be a type of
an Array,
anything that behaves like an array
Promise
any iterable object
collections
any observable like object
For example you can send a raw object like
myObj={name:'Jack',marks:100}
to of operator to convert to Observable.
obj$:Observable<any> = of(myObj);
but you can not send this raw object myObj to from operator simply because it is not iterable or array like collection.
for more detail : visit here
from operator may accept one of
promises
iterable
arrays
observable
from emits each individual item from the observable , can also do conversions.
of operator takes in the raw value and emits the value from the observable.
import {from, Observable, of} from 'rxjs';
const ofObs = of([1,2,3]);
const fromObs = from([2,3,4]);
const basicObs = Observable.create(observer=>{
observer.next(100);
observer.next(200);
observer.next(300);
})
const promise = new Promise((resolve,reject)=>{
resolve(100);
})
const array = [1,2,3];
const iterbale = "Dhana";
// const myObs = from(ofObs);//possible and can emit individual item value everytime 1, then ,2 , then 3
// const myObs = from(fromObs);//possbile and can emit individual item value everytime 1, then ,2 , then 3
// const myObs = from(basicObs);//possbile and can emit individual item value everytime 100, then ,200 , then 300
const myObs = from(promise);//possible can emit value 100
// const myObs = array(promise);//possible and can emit individual item value everytime 1, then ,2 , then 3
// const myObs = iterable(promise);//possible and can emit individual item value everytime D then h then a then n then a
myObs.subscribe(d=>console.log(d))
import {from, of} from 'rxjs';
const basicOf1 = of([1,2,3,4,5,6]) // emits entire array of events
const basicfrom1 = from([1,2,3,4,5,6]) //emits each event at a time
const basicOf2 = of(1,2,3,4,5,6) // emits each event at a time
// const basicfrom2 = from(1,2,3,4,5,6) //throws error
//Uncaught TypeError: number is not observable
const basicOf3 = of(...[1,2,3,4,5,6]) // emits each event at a time
const basicfrom3 = from(...[1,2,3,4,5,6]) //throws error
//Uncaught TypeError: number is not observable
basicOf3.subscribe(d=>console.log(d))
Here is the link to codepen

Resources