I have an endless stream of events and I need to limit them to 5, keep the rest paused for 3 seconds
So need to make a delay after every 5 calls
from([ 1,2,3,4,5,6,7,8,9,11,12,13,14,15,16,17,18,19,21,22,23,24,25,26,27,28 ])
.pipe(
// To demonstrate 1 after 1 values stream we use concatMap
// we return new Observalbe via of operator
// we pipe the delay for each element based on the index value we passed
// in our concatMap
concatMap((x,i) => of(x).pipe(
delayWhen((x) => {
console.log("im index: " + i);
// Not the first element, and every 5th element
return i !== 0 && i % 5 === 0 ? timer(3000): timer(0)})
))
)
.subscribe(x => console.log(x))
// Output: 1,2,3,4,5 ...delay 3s.... 6,7,8,9,10 ...delay 3s...
You can see in this stackblitz I made.
const stream = range(0, 100) // create dataset
.pipe(
bufferCount(5), // slice data into chunks
concatMap( // get this chunk
(msg) => of(msg).pipe(
delay(3000) // and emit every three seconds
))
)
stream.subscribe(item => console.log(item));
Related
My use case is as follows - i have a stream of operations for distinct elements and i want to only call "commit" on each object if they have been idle for a certain amount of time OR a different element is received.
I was trying around with groupBy and debounce, but did not get all the cases covered - e.g.
action.pipe(
groupBy(item -> item.key),
debounceTime(1000),
mergeMap(item -> {
item.commit()})
)
I'm not sure what is your goal:
Let's take the example of a situation where you have A => B => A coming within less than the minimum idle time
Option 1: each type of element should have each own idle-state - the second emission of type A will be ignored
Option 2. since there is no consecutive sequence the second A will not be ignored
OPTION 1 example:
action.pipe(
groupBy(item => item.key),
mergeMap(group => group.pipe(debounceTime(1000))),
mergeMap(item => item.commit())
)
Optionally:
const IDLE_TIME = XXXX;
action.pipe(
groupBy(item => item.key),
mergeMap(group => merge(
group.pipe(first()),
group.pipe(
timeInterval(),
filter(x => x.interval > IDLE_TIME),
map(x => x.value)
)
)),
mergeMap(item => item.commit())
)
OPTION 2 example:
action.pipe(
pairwise(),
debounce(([previous, current]) => previous.key == current.key? timer(1000) : EMPTY),
map(([pre, current]) => current),
mergeMap(item => item.commit())
)
You can assess the idle nature using auditTime, scan and filter
action.pipe(
//add the idle property to the item
map(item => ({ ...item, idle: false})),
//audit the stream each second
auditTime(1000),
//then use scan to with previous emission at audit time
scan(
(prev, curr) => {
//then if the key remains the same then we have an idle state
if (prev.key === curr.key) {
//return changed object to indicate we have an idle state
return Object.assign({}, curr, {idle: true});
} else {
//otherwise just return the non idle item
return curr
}
//seed with an object that cannot match the first emission key
}, { key: null }
),
//then filter out all emissions indicated as not idle
filter(item => item.idle === true)
//and commit
mergeMap(item => item.commit())
)
Then you can use distinctUntilKeyChanged to achieve the second condition
action.pipe(
distinctUntilKeyChanged('key'),
mergeMap(item => item.commit())
)
I'm not familiar with redux-observable but you would typically merge these two observables and then commit at the end.
I have a costly server ajax request which has one input (full: boolean). If full is false, the server can return either a partial or a full response (response.isFull == true); but if full is true, the server will return a full response. Normally the partial response is good enough, but there are certain conditions that will require a full response. I need to avoid requesting a full response explicitly as much as possible, so I thought I'd start with a BehaviorSubject which I can eventually feed with true and combine it with distinctUntilChanged if I ever need to get the full response. This will give me an observable with false initially and that can give me true if I feed that into it:
const fullSubject = new BehaviorSubject<boolean>(false);
Then I've got a function that takes a boolean parameter and returns an observable with the server request (retried, transformed, etc.). As said, the answer can be partial or full, but it can be full even if the input parameter was false at the server's discretion. For example:
interface IdentityData {
...
isFull: boolean;
}
private getSimpleIdentity(full: boolean): Observable<IdentityData> {
return Axios.get(`/api/identity${full?"?full=true":""}`)
.pipe( ... retry logic ...,
... transformation logic ...,
shareReplay(1) );
}
I need to know how can I combine these so that the following is true:
The server needs to be queried at most twice.
If the first answer is a full answer, no further queries must be performed to the server.
If the first answer is a partial answer, and true is fed into fullSubject, a full answer must be requested.
The expected output from all this is an observable that emits either one full response, or a partial response and, when asked, a full response.
Environment: Vue 2.6.11, RxJS 6.5.5, Axios 0.19.2, TypeScript 3.7.5.
Thanks in advance
Here would be my approach:
const fullSubject = new BehaviorSubject(false);
const src$ = fullSubject.pipe(
switchMap(isFull => Axios.get('...')),
take(2), // Server required at most twice
takeWhile(response => !response.isFull, true), // When `isFull`, it will complete & unsubscribe -> no more requests to the server
shareReplay(1),
);
src$.subscribe(() => { /* ... */ });
function getFullAnswer () {
fullSubject.next(true);
}
takeWhile takes a second argument, inclusive. When set to true, when the predicate function evaluates to false(e.g isFull is true) it will send that value as well. –
if I've got it correctly
private getSimpleIdentity(): Observable<IdentityData> {
return fullSubject.pipe(
switchMap(full => Axios.get(`/api/identity${full ? "?full=true" : ""}`)),
shareReplay(1),
);
}
Uses the retryWhen() operator
const source = of("").pipe(map(() => Math.floor(Math.random() * 10 + 1)));
const example = source
.pipe(
tap((val) => console.log("tap", val)),
map((val) => {
//error will be picked up by retryWhen
if (val !== 5) throw val;
return val;
}),
retryWhen((errors) =>
errors.pipe(
tap(() => console.log("--Wait 1 seconds then repeat")),
delay(1000)
)
)
)
.subscribe((val) => console.log("subscription", val));
/*
output:
tap 3
--Wait 1 seconds then repeat
tap 8
--Wait 1 seconds then repeat
tap 1
--Wait 1 seconds then repeat
tap 4
--Wait 1 seconds then repeat
tap 7
--Wait 1 seconds then repeat
tap 5
subscription 5
*/
I found many examples of how to reset timer, but they usually concerned manual reset (e.g. on-click button event).
I need a logic that will automatically reset the value when the countdown ends.
Timer:
type seconds = number;
const getRemainingTime$ = (store: Store): Observable<seconds> => {
// calculate fullTime based on the TriggerDate extracted from the State
// ...
return fullTime$.pipe(
switchMap((fullTime: seconds) =>
timer(0, 1000).pipe(
map((tickCount: number) => fullTime - tickCount),
takeWhile((remainingTime: seconds) => remainingTime >= 0)
)
)
);
}
Trigger (wait for 0 value on timer)
getRemainingTime$(this.store).pipe(
skipWhile((remainingTime: seconds) => remainingTime > 0),
)
.subscribe(data => {
const newTriggerDate: Date = new Date(new Date().getTime() + 60 * 1000); // +60 seconds
this.store.dispatch([new SetTriggerDateAction({ newTriggerDate })]);
});
...and it doesn't work -
When the remaining time is zero, the trigger goes crazy and dispatch an infinite number of actions. What is wrong?
PS: When I manually dispatch SetTriggerDateAction (onClick button), the problem disappears.
It was enough to replace skipWhile to a filter.
skipWhile
All values are emitted if the condition in skipWhile is met at least once.
filter
Only those values specified in the condition are emitted.
I have the following, and it does work, it keeps increasing the delay and eventually timing out which is what I wanted.
But because I am using Concatmap i lose the original value from the interval.
let x = 1
let source2$ = interval(500)
.pipe(
concatMap(() => {
x++
let newtime = x * 500
console.log("newtime ", newtime)
return of(5).pipe(delay(newtime))
}),
timeout(3000),
map((data) => {
return 'Source 2: ' + data
})
)
so it prints Source 2: 5.. where as i want it to print the value of the interval.
I got working what i wanted using the concatmap but i think its the wrong operator as I lose the original value.
Can somebody help?
More info
TO summarize, all i would like to do is emit values using the interval and after each emit increase the delay time - eventually it hits the timeout of 3000 ms and errors out.
I've mentioned in comments that you can use concatMap for this that receives ever increasing index from interval:
concatMap(index => {
let newtime = index * 500
console.log("newtime ", newtime)
return of(index).pipe(delay(newtime))
}),
Notice, that I'm returning the value back to the stream by of(index).
I think I understand what were you concerned about returning another Observable. Since you want to emit items in sequence (emit one only after the previous one completes) then you have to use concatMap with another inner Observable. There isn't a special operator only for this functionality because this is "composable behavior" which means you can achieve this behavior by combining existing operators.
const source2$ = interval(500)
.pipe(
map(x => x * 500),
switchMap(x => timer(x)),
timeout(3000),
map(data => 'Source 2: ' + data)
)
UPDATE:
https://stackblitz.com/edit/rxjs-iywcm6?devtoolsheight=60
const source2$ = interval(500)
.pipe(
tap(x => console.log('Tick before delay', x)),
concatMap(x => timer((x + 1) * 500).pipe(mapTo(x))),
tap(x => console.log('Tick after delay', x)),
map(data => 'Source 2: ' + data),
timeout(3000)
).subscribe(
(data) => console.log(data),
e => console.error('Timeout', e))
I am trying RxJS.
My use case is to parse a log file and group lines by topic ( i.e.: the beginning of the group is the filename and then after that I have some lines with user, date/time and so on)
I can analyse the lines using regExp. I can determine the beginning of the group.
I use ".scan" to group the lines together, when I've the beginning of new group of line, I create an observer on the lines I've accumulated ... fine.
The issue is the end of the file. I've started a new group, I am accumulating lines but I can not trigger the last sequence as I do not have the information that the end. I would have expect to have the information in the complete (but not)
Here is an example using number. Begin of group can multi of 3 or 5. (remark: I work in typescript)
import * as Rx from "rx";
let r = Rx.Observable
.range(0, 8)
.scan( function(acc: number[], value: number): number[]{
if (( value % 3 === 0) || ( value % 5 === 0)) {
acc.push(value);
let info = acc.join(".");
Rx.Observable
.fromArray(acc)
.subscribe( (value) => {
console.log(info, "=>", value);
});
acc = [];
} else {
acc.push(value);
}
return acc;
}, [])
.subscribe( function (x) {
// console.log(x);
});
This emit:
0 => 0
1.2.3 => 1
1.2.3 => 2
1.2.3 => 3
4.5 => 4
4.5 => 5
6 => 6
I am looking how to emit
0 => 0
1.2.3 => 1
1.2.3 => 2
1.2.3 => 3
4.5 => 4
4.5 => 5
6 => 6
7.8 => 7 last items are missing as I do not know how to detect end
7.8 => 8
Can you help me, grouping items?
Any good idea, even not using scan, is welcome.
Thank in advance
You can use the materialize operator. See the documentation here and the marbles here, and an example of use from SO.
In your case, I would try something like (untested but hopefully you can complete it yourself, note that I don't know a thing about typescript so there might be some syntax errors):
import * as Rx from "rx";
let r = Rx.Observable
.range(0, 8)
.materialize()
.scan( function(acc: number[], materializedNumber: Rx.Notification<number>): number[]{
let rangeValue: number = materializedNumber.value;
if (( rangeValue % 3 === 0) || ( rangeValue % 5 === 0)) {
acc.push(rangeValue);
generateNewObserverOnGroupOf(acc);
acc = [];
} else if ( materializedNumber.kind === "C") {
generateNewObserverOnGroupOf(acc);
acc = [];
} else {
acc.push(rangeValue);
}
return acc;
}, [])
// .dematerialize()
.subscribe( function (x) {
// console.log(x);
});
function generateNewObserverOnGroupOf(acc: number[]) {
let info = acc.join(".");
Rx.Observable
.fromArray(acc)
.subscribe( (value) => {
console.log(info, "=>", value);
});
The idea is that the materialize and dematerialize works with notifications, which encodes whether the message being passed by the stream is one of next, error, completed kinds (respectively 'N', 'E', 'C' values for the kind property). If you have a next notification, then the value passed is in the value field of the notification object. Note that you need to dematerialize to return to the normal behaviour of the stream so it can complete and free resources when finished.