Retrieve interval value from an observable - rxjs

I've created an observable O, with an interval of x. This is a silly question and it seem like it should be easy, but how do I find out O's interval. I can't just say something such as, O.getIntervalValue(), it seems like it is much more complicated than that?
combineLatest(Online, Mode, TestFrequency, watchRunTest()).pipe(
switchMap((_Properties): Observable<any> => {
return _Properties[0] && _Properties[1] === "auto" ? interval(
_Properties[2] * 60 * 1000) : never();
}))
.subscribe((_RunTest) => {
this.setRunTest(new Date().getTime(), null)
});

Related

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();

Observable make request multiple times and collect response together

In database I have 19 users.
In my API, I can get only 5 results in one call.
If I want to get all them, I need to do request 4 times, each time to get 5 users. With start query I will change from which position I want new users to get.
I'm trying to do it in RxJS together with redux-observable.
I have some idea, but maybe my approach is imperative, and RxJS is opposite ideology.
// get users from API and `pipe` them helps me to see actual data and to count length of array
function getUsers(position = 0) {
return ajax.getJSON(`${API}/users?_start=${position}&_limit=5`).
pipe(map(({data}) => ({responseLength: data.length, data})))
}
// here when I got response if array.lenght is equal to 5, I know that I need to do fetch of data again.
// Problem is encountered here: if I do recursion after doing I will get only last result, not both of them,
// if I put my previous result into array, and then recursion result again push in array it become too complicated after
// in userFetchEpic to manipulate with this data
function count(data) {
return data.pipe(
map(item => {
if (item.responseLength === 5) {
count(getUsers(5));
}
return {type: "TEST" , item}
})
)
}
function userFetchEpic(action$) {
return action$
.pipe(
ofType(USER_FETCH),
mergeMap(() => {
return count(getUsers()).pipe(
map(i => i)
)
})
);
}
My code is here just to show what was my way of thinking.
Main problem is in recursion how to save all values together, if I save values in array.
Then I need to loop through array of observables and that sounds complicated in my head. :)
Probably this problem have much easier and better solution.
19 Users with 4 concurrent calls
I've re-arranged your get-users function to generate 4 Ajax calls, run them all concurrently, then flatten the result into one array. This should get you all 19 users in a single array.
function getUsers() {
return forkJoin(
[0,5,10,15].map(position =>
ajax.getJSON(`${API}/users?_start=${position}&_limit=5`)
)
).pipe(
map(resArray => resArray.flatMap(res => res.data))
)
}
function userFetchEpic(action$) {
return action$.pipe(
ofType(USER_FETCH),
mergeMap(_ => getUsers())
);
}
Generalize: Recursively get users
This, again, will return all 19 users, but this time you don't need to know that you have 19 users ahead of time. On the other hand, this makes all its calls in sequence, so I would expect it to be slower.
You'll notice this one is done recursively. You are creating a call stack this way, but so long as you don't have millions of users, it shouldn't be a problem.
function getUsers(position = 0) {
return ajax.getJSON(`${API}/users?_start=${position}&_limit=5`).pipe(
switchMap(({data}) =>
data.length < 5 ?
of(data) :
getUsers(position + 5).pipe(
map(recursiveData => data.concat(recursiveData))
)
})
);
}

Reset Scan Accumulator RxJS after certain time

I have a fromEvent attached to input keydown event. This way I can listen to KeyEvents.
Inside the pipe, I use the scan operator, so I can accumulate the latest 3 keys introduced by the user.
I check in the scan, the accumulator length, so if it's already three, I clean it up (manual reset).
I need a way that when the user types in, within the next 3000ms, he can keep typing until reaching the limit (3 keys) but if the user is slower than the time limit (3s), next time he types in, I will reset the accumulator manually.
fromEvent(myInput.nativeElement, 'keydown').pipe(
tap(e => e.preventDefault()),
scan((acc: KeyboardEvent[], val: KeyboardEvent) => {
// Add condition here to manually reset the accumulator...
if (acc.length === 3) {
acc = [];
}
return [...acc, val];
}, []),
takeUntil(this.destroy$)
).subscribe((events: KeyboardEvent[]) => console.log(events));
I have tried to merge this with a timer in some way, but I can't figure out how. Not sure how to get there.
you can use the timeInterval operator here, which gives you the time passed between emissions along with the value. set up along the lines of:
fromEvent(myInput.nativeElement, 'keydown').pipe(
tap(e => e.preventDefault()),
timeInterval(), // just add this operator, will convert to shape {value: T, interval: number}
scan((acc: KeyboardEvent[], val) => {
// Add condition here to manually reset the accumulator...
// also check the interval
if (acc.length === 3 || val.interval > 3000) {
acc = [];
}
return [...acc, val.value];
}, []),
takeUntil(this.destroy$)
).subscribe((events: KeyboardEvent[]) => console.log(events));
here is a working blitz: https://stackblitz.com/edit/rxjs-dxfb37?file=index.ts
not an operator I've ever had a use case for before, but seems to solve your issue here pretty effectively.

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

Maintaining Subject emission order when invoking next within subscription

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

Resources