I have an observable:
messages: string[] = ['a', 'b', 'c'];
const source = from(messages)
How do you delay it so when anyone subscribes to it, it will delay for n second to emit the items?
So:
source.subscribe(i => console.log(i));
// output ...n seconds... 'a' ...n seconds... 'b' ...n seconds... 'c'
You can combine the stream with an interval using zip:
zip(
from(['a', 'b', 'c', 'd']),
interval(1000),
(a, b) => a
)
.subscribe(console.log);
zip would combine the nth elements of the each stream into an array. That's way we use a selector function: (a, b) => a. It ensures that only elements from the first stream are used. The interval stream is only used for delaying emission.
I had the same problem and I solved it as the following code
const {from, of} = rxjs;
const {concatMap, delay} = rxjs.operators;
from(['a', 'b', 'c'])
.pipe(concatMap((msg) => of(msg).pipe(delay(1000))))
.subscribe(console.log);
<script src="https://cdnjs.cloudflare.com/ajax/libs/rxjs/6.4.0/rxjs.umd.min.js"></script>
You can just use the .delay() operator:
messages: string[] = ['a', 'b', 'c'];
const source = from(messages).pipe(
delay(1000)//delay for 1 second
)
Remember to import delay:
import { delay } from 'rxjs/internal/operators';
const source = from(['a', 'b', 'c', 'd']);
const delayPerElements = source
.pipe(map(v => of(v).pipe(delay(1000))), concatAll());
delayPerElements.subscribe(it => console.log(it));
// ... a ... b ... c ... d
I don't know if this is the best way but it works for me. Hope this help someone in the future.
As #Igno Burk suggestion:
const source = from(['a', 'b', 'c', 'd']);
const delayPerElements = source
.pipe(concatMap(v => of(v).pipe(delay(1000))));
delayPerElements.subscribe(it => console.log(it));
// ... a ... b ... c ... d
return delayPerElements;
Related
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))
If I have an array of events that include a utc timestamp and event data like as follows:
[{utcts: , data: , ... ];
how would you use RxJS to "replay" those events with the correct time differentials between each item in the array? Assume the array is ordered by the utcts field so the first item has the lowest value.
here is a very basic set of data to get started:
var testdata = [
{utcts: 1, data: 'a'},
{utcts: 4, data: 'b'},
{utcts: 6, data: 'c'},
{utcts: 10, data: 'd'}
];
Assume the utcts is just the number of seconds from the start of replaying the event which starts at 0 seconds.
Use delayWhen to give you timed replay.
Since utcts given is relative (not absolute) time, don't need to refresh the timestamp inside the data object.
I have added a timestamp to the console log so we can see the elapsed output time.
Note the extra few milliseconds is typical of rxjs process time.
console.clear()
const testdata = [
{utcts: 1, data: 'a'},
{utcts: 4, data: 'b'},
{utcts: 6, data: 'c'},
{utcts: 10, data: 'd'}
];
const replayData = (data) => Rx.Observable.from(data)
.delayWhen(event => Rx.Observable.of(event).delay(event.utcts * 1000))
// Show replay items with output time (in milliseconds)
const start = new Date()
replayData(testdata)
.timestamp()
.subscribe(x => console.log(x.value, 'at', x.timestamp - start, 'ms'))
<script src="https://cdnjs.cloudflare.com/ajax/libs/rxjs/5.5.6/Rx.js"></script>
Ref delayWhen, timestamp
This also works, arguably simpler, not sure which is best.
mergeMap() flattens the inner observable, which is necessary to apply the delay.
const replayData = (data) => Rx.Observable.from(data)
.mergeMap(event => Rx.Observable.of(event).delay(event.utcts * 1000))
Rough pseudo (comes out of my head directly without running to verify) might be something similar to
Observable.scan((acc, value) => ({
delay: delay === NaN ? value.utcts - delay,
value
}), { delay: NaN, value: null })
.mergeMap(({delay, value}) => Observable.from(value).delay(delay))
scan operator is similar to reduce, but emits intermediate values. Using that compute diff between times to get delay those values, then emit values per given delayed time. There are couple of other approaches could work in same way.
This should work in https://rxviz.com (copy-paste there):
const { delay, mergeMap } = RxOperators;
const { from, Observable, of } = Rx;
const testdata = [
{utcts: 0.2, data: 'a'},
{utcts: 2.0, data: 'b'},
{utcts: 2.8, data: 'c'},
{utcts: 4.0, data: 'd'}
];
from(testdata).pipe(
mergeMap(event => of(event).pipe(
delay(event.utcts * 1000)
))
)
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.
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.