Maintaining Subject emission order when invoking next within subscription - rxjs

I'm running into a bug and I've determined it's due to the fact that Subjects when next()ed will fire their events synchronously.
The following code produces the following ouput:
mySubject.subscribe(score => {
if (score === 2) {
mySubject.next(score + 10);
}
})
mySubject.subscribe(score => console.log(score))
Ouput:
1
12
2
The only way Im able to get the proper output (1,2,12) is if I wrap my next() call in a setTimeout to make it async. Is there a proper way to deal with this issue that I'm missing?

If you're using RxJS 5.5 I'd personally use setTimeout as well. There's subscribeOn operator that you could use with the async scheduler (import { async } from 'rxjs/scheduler/async') to run every emission in a new frame but it's not available in RxJS 5.5 right now.
So probably the easiest way is using delay(0) which doesn't make any delay and passes everything asynchronously like you did with setTimeout():
import { Subject } from 'rxjs/Subject';
import { delay } from 'rxjs/operators';
const mySubject = new Subject();
const source = mySubject.pipe(delay(0));
source.subscribe(score => {
if (score === 2) {
mySubject.next(score + 10);
}
})
source.subscribe(score => console.log(score));
mySubject.next(1);
mySubject.next(2);
See live demo (open console): https://stackblitz.com/edit/typescript-fiwgrk?file=index.ts

How about this?
const plusTen$ = mySubject.filter(score => score === 2).map(score => score + 10);
mySubject.merge(plusTen$).subscribe(score => console.log(score))

Don't know if it helps the OP, but to anyone who encounters this situation:
It's strictly speaking not a bug because when you next() a Subject, all subscribers are iterated and called. So if you next() within the first subscriber, the iteration starts again and the second subscriber of the first iteration is called after the second iteration is done.
Note that because of this, the order of subscription is relevant. So alternatively to using setTimout or delay as suggested in the accepted answer, you could also swap the subscriptions like this:
mySubject.subscribe(score => console.log(score));
mySubject.subscribe(score => {
if (score === 2) {
mySubject.next(score + 10);
}
})
Output:
1
2
12

Related

How to achieve an RxJS operator like concatMap() with a queue size of 1?

concatMap() will exhaust input observables in order. It will move on to the next input observable if the current one completes. Input observables will be queued and processed one by one. This is almost what I want.
I want a slightly different behavior: I want an operator that does the same as concatMap(), but with a queue length of 1. That is, an operator that will exhaust the current observable, but while doing that, keep only the most recent new input observable, instead of queueing them all. How can I achieve this?
Edit: switchMap() is not a solution, because I don't want to cancel the current observable when the next one arrives. I want to exhaust it and then move on to the most recent next one.
Edit 2: exhaustMap() is not a solution, because it will ignore subsequent observables as long as one is still being consumed.
Here you go. Create a custom operator based on mergeMap. Cache the latest value and once active observable completes throws the latest observable into the stream and repeat.
Full example here.
function concatLatest<T, O extends Observable<unknown>>(
project: (value: T, index: number) => O
): OperatorFunction<T, ObservedValueOf<O>> {
let subj: BehaviorSubject<O> | null = null;
let latestObservable: O = null;
return mergeMap((val, i) => {
latestObservable = defer(() =>
project(val, i).pipe(
tap({
complete: () => {
if (latestObservable) {
subj.next(latestObservable);
latestObservable = null;
} else {
subj.complete();
subj = null;
}
},
})
)
) as O;
if (subj) {
return EMPTY;
}
subj = new BehaviorSubject(latestObservable);
return subj.pipe(mergeAll());
});
}

RxJS and optional inner query

I have a kind of web form which has 2 parts (master and optional child) and want to save the form values
A first call is for master data. The call result is used as a parameter for second call. But in some cases we don't need to save child part.
In the end of saving in the subscribe section I want to show "Success" message with one field from the first call result. I mean we can have one or two calls but show the success message when both of them completed
Master Data Call -> Child Data Call (optional) -> Success message
How to implement the behaviour using RxJS in a proper way?
Addition:
Based on comments I created an example here:
https://rxjs.rxplayground.com/
var master = from([1,2])
var child = of(10)
master.pipe(concatMap(m=>(m==1)?child:EMPTY)).subscribe(res=>console.log(res))
(shows 10 but should be 1,2)
Try to change "master" value. In any case log output should match the master. Also it would be nice to avoid "of(m)" inside of concatMap call
One more addition:
I tried this but not sure that it is the best solution
var master = from([1,2]);
var child = of("*");
master.pipe(
mergeMap(m => (m === 1 ? child.pipe(mapTo(m)) : of(m)))
).subscribe(console.log);
(shows 1,2)
I was saying this,just check once
import { from, of } from 'rxjs';
import { concatMap } from 'rxjs/operators';
const master$ = from([1, 2, 3, 4]);
const child$ = of(20);
// const child$ = () => console.log("child called");
master$
.pipe(
concatMap((masterData) => {
if (masterData % 2 === 0) {
child$;
// child$(); // if you want to check,child is being called or not
}
return of(masterData);
})
)
.subscribe(console.log);
This solution does exactly what I need
var master = from([1,2]);
var child = of("*");
master.pipe(
mergeMap(m => (m === 1 ? child.pipe(mapTo(m)) : of(m)))
).subscribe(console.log);
(shows 1,2)

Using RxJS to remove nested callbacks when things must be done in sequence

I need to do one HTTP request after another, but the second one can't start until after the first one has finished because the second needs as a parameter a value returned from the first.
Here it is with a nested callback which is great because it works and it's fairly clear from reading the code what is happening.
this.isLoading = true;
this.firstService.get(this.id)
.subscribe((response: FirstReturnType) => {
this.firstThing = response;
this.secondService.get(this.firstThing.secondId)
.subscribe(
(response: SecondReturnType) => {
this.secondThing = response;
this.isLoading = false;
}
}
The claim I see people making is that nested callbacks are bad and that one should use RxJS to make it better.
However, nobody making these claims has been able to produce a working example. Can you?
Your Code Re-written
Here is some code that has a 1-1 correspondence with your code, but it is flattened
this.isLoading = true;
this.firstService.get(this.id).pipe(
mergeMap((response: FirstReturnType) => {
this.firstThing = response;
return this.secondService.get(response.secondId);
})
).subscribe((response: SecondReturnType) => {
this.secondThing = response;
this.isLoading = false;
});
What this gets right: you're using a higher-order observable operator to map a value emitted by one observable into a new observable that you subscribe to. In this case, mergeMap is subscribing for you and getting rid of your nesting.
For Your Consideration
Consider this. The following is about as clean looking at six service calls (each giving some value to the next one) in a row can get if you're not using a higher-order operator:
this.firstService.getThing("First").subscribe(result1 => {
this.secondService.getThing(result1.value).subscribe(result2 => {
this.thirdService.getThing(result2.value).subscribe(result3 => {
this.fourthService.getThing(result3.value).subscribe(result4 => {
this.fifthService.getThing(result4.value).subscribe(result5 => {
this.sixthService.getThing(result5.value).subscribe(result6 => {
console.log("Result Six is: " + result6.value);
});
});
});
});
});
});
Here's the exact same thing with mergeMap:
this.firstService.getThing("First").pipe(
mergeMap(result1 => this.secondService.getThing(result1.value)),
mergeMap(result2 => this.thirdService.getThing(result2.value)),
mergeMap(result3 => this.fourthService.getThing(result3.value)),
mergeMap(result4 => this.fifthService.getThing(result4.value)),
mergeMap(result5 => this.sixthService.getThing(result5.value)),
).subscribe(result6 => {
console.log("Result Six is: " + result6.value);
});
If that's not enough to convince you, you can lean a little bit more into some functional programming to make this even cleaner (without repetitively naming each result)
const passValueToService = service => result => service.getThing(result.value);
passValueToService(this.firstService)("First").pipe(
mergeMap(passValueToService(this.secondService)),
mergeMap(passValueToService(this.thirdService)),
mergeMap(passValueToService(this.fourthService)),
mergeMap(passValueToService(this.fifthService)),
mergeMap(passValueToService(this.sixthService)),
).subscribe(finalResult => {
console.log("Result Six is: " + finalResult.value);
});
Or why not lean EVEN harder and keep our list of services in an array?
const [firstS, ...restS] = [this.firstService, this.secondService, this.thirdService, this.fourthService, this.fifthService, this.sixthService];
const passValueToService = service => result => service.getThing(result.value);
passValueToService(firstS)("first").pipe(
...restS.map(service => mergeMap(passValueToService(service)))
).subscribe(finalResult => {
console.log("Result Six is: " + finalResult.value);
});
None of these simplifications are very easily done while nesting subscribe calls. But with the help of some functional currying (and the handy RxJS pipe to compose with), you can begin to see that your options expand dramatically.
Understanding concatMap, mergeMap, & switchMap
The Setup
We'll have 3 helper functions as described here:
/****
* Operator: intervalArray
* -----------------------
* Takes arrays emitted by the source and spaces out their
* values by the given interval time in milliseconds
****/
function intervalArray<T>(intervalTime = 1000): OperatorFunction<T[], T> {
return s => s.pipe(
concatMap((v: T[]) => concat(
...v.map((value: T) => EMPTY.pipe(
delay(intervalTime),
startWith(value)
))
))
);
}
/****
* Emit 1, 2, 3, then complete: each 0.5 seconds apart
****/
function n123Stream(): Observable<number> {
return of([1,2,3]).pipe(
intervalArray(500)
);
}
/****
* maps:
* 1 => 10, 11, 12, then complete: each 1 second apart
* 2 => 20, 21, 22, then complete: each 1 second apart
* 3 => 30, 31, 32, then complete: each 1 second apart
****/
function numberToStream(num): Observable<number>{
return of([num*10, num*10+1, num*10+2]).pipe(
intervalArray(1000)
);
}
The above mapping function (numberToStream), takes care of the map part of concatMap, mergeMap, and switchMap
Subscribing to each operator
The following three snippits of code will all have different outputs:
n123Stream().pipe(
concatMap(numberToStream)
).subscribe(console.log);
n123Stream().pipe(
mergeMap(numberToStream)
).subscribe(console.log);
n123Stream().pipe(
switchMap(numberToStream)
).subscribe(console.log);
If you want to run these back-to-back:
concat(
...[concatMap, mergeMap, switchMap].map(
op => n123Stream().pipe(
op(numberToStream),
startWith(`${op.name}: `)
)
)
).subscribe(console.log);
concatMap:
concatMap will not subscribe to the second inner observable until the first one is complete. That means that the number 13 will be emitted before the second observable (starting with the number 20) will be subscribed to.
The output:
10 11 12 20 21 22 30 31 32
All the 10s are before the 20s and all the 20s are before the 30s
mergeMap:
mergeMap will subscribe to the second observable the moment the second value arrives and then to the third observable the moment the third value arrives. It doesn't care about the order of output or anything like that.
The output
10 20 11 30 21 12 31 22 32
The 10s are earlier because they started earlier and the 30s are later because they start later, but there's some interleaving in the middle.
switchMap
switchMap will subscribe to the first observable the moment the first value arrives. It will unsubscribe to the first observable and subscribe to the second observable the moment the second value arrives (and so on).
The output
10 20 30 31 32
Only the final observable ran to completion in this case. The first two only had time to emit their first value before being unsubscribed. Just like concatMap, there is no interleaving and only one inner observable is running at a time, but some emissions are effectively dropped.
You can use switchMap.
this.firstService.get(this.id)
.pipe(
tap((response: FirstReturnType) => this.firstThing = response),
switchMap((response: FirstReturnType) => this.secondService.get(response.secondId)),
tap((response: SecondReturnType) => {
this.secondThing = response;
this.isLoading = false;
})
).subscribe();

Delay for every element with RXJS

I'm using RxViz to simulate different actions that comes every 1 sec. When I try
Rx.Observable.create(obs => {
obs.next([1, 2, 3]); // or could be ['aaa', 'bbbb', 'ccc']
obs.complete();
}).delay(1000);
on https://rxviz.com
or on my own with a console.log
it keeps displaying the three number 1, 2, 3 at the same time
There's a post about this same problem, but none of the answer works for me. I'm using Rx last version 6
How can I create an observable with a delay
[EDIT] The array can contains anything like number, string or any object
If you want to delay each value (by 1 sec for example), you may do something like the following:
Rx.Observable.create(obs => {
obs.next([1, 2, 3]);
obs.complete();
})
.pipe(
// make observable to emit each element of the array (not the whole array)
mergeMap((x: [any]) => from(x)),
// delay each element by 1 sec
concatMap(x => of(x).pipe(delay(1000)))
)
.subscribe(x => console.log(x));
}
Here I did not modify the internals of the observable created by you. Instead, I just take your observable and apply appropriate operations to achieve what you seem to be expecting.
Here is my solution (very clean)
const fakeData = [1,2,3]
loadData$() {
return from(fakeData).pipe(
concatMap(item => of(item).pipe(
delay(1000)
)),
);
}
This one works by modifying a little bit #siva636's answer
Rx.Observable.create(obs => {
obs.next(1);
obs.next(2);
obs.next(3);
obs.complete();
}.concatMap(x=>Rx.Observable.of(x) .delay(1000) )
Here is a succinct way that builds on the other responses.
from([...Array(10).keys()]).pipe(
concatMap(x => of(x).pipe(delay(1000)))
).subscribe(y => console.log(y))
A more RxJs native version would be as follows.
const myInterval = rxjs.interval(1000);
myInterval.pipe(rxjs.operators.take(10)).subscribe(x => console.log(x));
Here, you emit in one observable emission the all array. [1,2,3].
You only delay that one emission by 1000 ms. But the emission is still one.
Even if we emit each value on its own, the delay function will only apply to the first emission. The others will come immediately after:
Rx.Observable.create(obs => {
var arr = [1, 2, 3];
arr.forEach(item => obs.next(item));
obs.complete();
}).delay(1000);
There is no magic in the create constructing function. If we want an emission to come every x time:
We could make an interval that emits those values (taken from learnrxjs)
import { Observable } from 'rxjs/Observable';
/*
Increment value every 1s, emit even numbers.
*/
const evenNumbers = Observable.create(function(observer) {
let value = 0;
const interval = setInterval(() => {
observer.next(value);
value++;
}, 1000);
return () => clearInterval(interval);
});
RxJS v7 supports the operator delayWhen [1], so you could write a simpler code as
import { delayWhen, interval, of } from 'rxjs';
of("John", "von", "Neumman", "János Neumann").pipe(
delayWhen((_, index) => interval(index*1000))
).subscribe(console.log);
Check out a demo on https://stackblitz.com/edit/vgibzv?file=index.ts
It works because it delays the emission of items by 0 seconds, 1000 seconds, 2000 seconds, 3000 seconds, and so on.
Another choice is the operator scan, you make the series from an interval [2].
[1] "RxJS - delayWhen." 16 Dec. 2022, https://rxjs.dev/api/operators/delayWhen
[2] "RxJS - scan." 16 Dec. 2022, rxjs.dev/api/index/function/scan

Keep delaying HTTP request until new params are arriving

Suppose we have a function getIds() which takes an array of some ids
like this:
getIds([4, 1, 32]);
This function will delay HTTP call for 100ms. But during 100ms if this
same function is called again:
getIds([1, 8, 5]);
It will reset the 100ms timer and keep merging the passed ids. It will
send HTTP request only if it's not called by anyone for more than 100ms.
I am new to RxJS and here's my attempt to solve this problem but I have
a feeling that there could be better solution for this problem.
https://jsfiddle.net/iFadey/v3v3L0yd/2/
function getIds(ids) {
let observable = getIds._observable,
subject = getIds._subject;
if (!observable) {
subject = getIds._subject = new Rx.ReplaySubject();
observable = getIds._observable = subject
.distinct()
.reduce((arr, id) => {
arr.push(id);
return arr;
}, [])
// Some HTTP GET request will go here
// whose results may get flatMapped here
.publish()
.refCount()
;
}
ids.forEach((id) => {
console.log(id);
subject.next(id);
});
clearTimeout(getIds._timer);
getIds._timer = setTimeout(() => {
getIds._observable = null;
getIds._subject = null;
subject.complete();
}, 100);
return observable;
}
getIds([1, 2, 3])
.subscribe((ids) => {
console.log(ids);
});
getIds([3, 4, 5])
.subscribe((ids) => {
console.log(ids);
});
edit:
I am looking for an operator which behaves like debounce but without dropping previous values. Instead it must queue them.
I am not certain to have captured exactly which of the following you are looking for, so I will simply describe both. There are two "time based patterns" that are most often suited for this type of problem in my experience:
debounce
rxmarbles url: http://rxmarbles.com/#debounce ; github doc
As it says in its documentation, it
Emits an item from the source Observable after a particular timespan
has passed without the Observable omitting any other items.
throttle
rxmarbles url: none yet ; github doc
Returns an Observable that emits only the first item emitted by the
source Observable during sequential time windows of a specified
duration.
Basically, if you would like to wait until the inputs have quieted for a certain period of time before taking action, you want to debounce. If you do not want to wait at all, but do not wish to make more than 1 query within a specific amount of time, you will want to throttle.
Hope it makes sense.

Resources