I am studying
operator map v.s. flatmap
how to add promise into observable chain.
Then I constructed 4 different versions of var source as below.
version 1, 3 works as expected, while version 2, 4 fail oddly.
My code has also been added in => js bin
Could someone tell what is wrong with my code?
Thanks,
Xi
console.clear();
var p = new Promise((resolve, reject) => {
setTimeout( () => {
resolve('resolved!');
} , 1000);
});
var source = Rx.Observable.interval(200).take(3)
.flatMap(x => Rx.Observable.timer(500).map(() => x)) //version 1, works OK
// .flatMap(x => Rx.Observable.timer(500).map((x) => x)) // version 2, not OK, returns => 0, 0, 0
// .map(x => p.then( s => console.log(s))); // version 3, works OK
// .flatMap(x => p.then( s => console.log(s))); // version 4, not OK, error occurs
source.subscribe(x => console.log(x.toString()));
.flatMap(x => Rx.Observable.timer(500).map((x) => x))
returns "0", "0", "0" because timer emits 0 after 500 ms and map takes that value as input x and returns it with (x) => x. In the previous line, x was not redeclared in the map, so it came from flatMap.
.flatMap(x => p.then( s => console.log(s)));
gives an error because a promise emits the return value of the then callback. That's console.log(s) which being a statement evaluates to undefined. So the flatMap gives an Observable of undefined, undefined, undefined. When the first reaches the subscribe it tries to do undefined.toString and errors out.
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 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));
So say I have these 3 observables:
const numpad1$ = fromEvent(document, 'keydown')
.pipe(
startWith(0),
filter(({ key, code }) => key === '1' && code === 'Numpad1'),
map(() => 1),
scan((a, b) => a + b, 0),
);
const numpad2$ = fromEvent(document, 'keydown')
.pipe(
startWith(0),
filter(({ key, code }) => key === '2' && code === 'Numpad2'),
map(() => 1),
scan((a, b) => a + b),
);
const numpad3$ = fromEvent(document, 'keydown')
.pipe(
startWith(0),
filter(({ key, code }) => key === '3' && code === 'Numpad3'),
map(() => 1),
scan((a, b) => a + b),
);
Then I use combineLatest like this:
combineLatest(numpad1$, numpad2$, numpad3$).subscribe(console.log)
The subscription doesn't do a console log until numpad 1, 2 and 3 all have been pressed atleast once.
Is there an rxjs operator I can use to have an observable emit whenever any of the inner obersvables have emitted?
For example, in the example above, i'm expecting the console.log to be called when any numpad 1 is pressed for the first time, while numpad 2 and 3 haven't been pressed at all. So the result should be like this: [1, 0, 0] (0s because i'm using startWith(0)).
The solution was actually to just move the startWith(0) to the end of the pipeline.
so:
const numpad1$ = fromEvent(document, 'keydown')
.pipe(
filter(({ key, code }) => key === '1' && code === 'Numpad1'),
map(() => 1),
scan((a, b) => a + b, 0),
startWith(0),
);
The startsWith is filtered out by the filter operator, I suggest using a map function here. For instance:
const numpad3$ = fromEvent(document, 'keydown')
.pipe(
startWith(0),
map(({ key, code }) => return key === '3' && code === 'Numpad3' ? 1 : 0)
);
This will return one if it meets the conditions and 0 else.
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.