When I run the code below I get the desired result followed by a re-subscription of the delayed shared observable when the other observable completes.
const source = interval(1000).pipe(
take(5),
share());
source.subscribe(x => console.log('c1', x));
source.pipe(
delay(2000), // doesn't delay subscription buffers the output from interval observable
switchMapTo(source)
).subscribe(x => console.log('c2', x));
output :
c1 0
c1 1
c1 2
c1 3
c2 3
c1 4
c2 4
c2 0
c2 1
c2 2
c2 3
c2 4
This is recursive. Source emissions cause source subscriptions.
source.pipe( // eventually "subscribes" to the source
delay(2000), // doesn't delay subscription buffers the output from interval observable
switchMapTo(source) // subscribes again to the source after every emission
Maybe what you want is:
timer(2000).pipe( // eventually subscribes to a timer
switchMapTo(source) // Subscribes to source after 2s (timer emits)
);
Related
I am trying to setup a poller using expand but the behavior is not what I want
https://stackblitz.com/edit/rxjs-finalize-unsubscribe-6xy2yb?file=index.ts
checkExistence produces a random boolean - With the expand, I expect a recursive delayed call of the same checkExistence function producing random booleans every 5 seconds (after one initial call).
I also expect the stop to kick in 30seconds and stop the polling and 5 seconds later resume the random boolean stream. Any pointers will help.
Instead I get the same boolean value getting printed; also after the start is triggered, it produces batches of booleans together.
This isn't really a single question.
Assignment/function Invocation
A simplified example
The equals operator assigns a value to a variable. Function invocation returns a value
function inc(n){
return n + 1
}
const result = inc(5)
console.log(result); // 6
Here, the result holds the number value 6. Not a function that calculated 5 + 1. 5 + 1 only happens once here.
Now consider this function that randomly increments a number by either 1 or 2
function bump(n){
const nu = Math.random();
return nu < 0.5 ? n + 1 : n + 2
}
const result = bump(5);
console.log(result); // 7
console.log(result); // 7
console.log(result); // 7
console.log(result); // 7
console.log(result); // 7
Every time I print the result, it's the same. bump was only called once and only generated a random number once.
function bump(n){
const nu = Math.random();
return nu < 0.5 ? n + 1 : n + 2
}
console.log(bump(5)); // 7
console.log(bump(5)); // 7
console.log(bump(5)); // 6
console.log(bump(5)); // 7
console.log(bump(5)); // 6
Here, bump is called 5 times, and each time it generated a new random number.
Fixing checkExistence
There are two ways.
Instead of generating a value once and re-using it. Generate a new boolean in your poll every time you need one.
function checkExistencePoll(): Observable<boolean> {
const POLL = 5000;
return checkExistence().pipe(
expand(_ =>
timer(POLL).pipe(
switchMap(() => checkExistence()),
takeUntil(stop),
repeatWhen(() => start)
)
)
);
}
Have checkExistence return an observable that generates a new boolean on subscription
function checkExistence(): Observable<boolean> {
return defer(() => {
const nu = Math.random();
const rand = nu < 0.5;
console.log(nu, rand);
return of(rand);
});
}
Handling subscriptions
In your code, expand is generating an observable that subscribes to a subject ( called start) in order to decide when to repeat. Every observable expand creates does this. So each one repeats when start emits. You should expect batches of booleans equal to the number of concurrent observables you've created.
This came up in the context of an app that runs some relatively costly computations in a web worker, and has to prioritize some of these computations over others (for example those affecting the UI over those needed to sync data with the server).
As a simple example, suppose I have some observables that asynchronously process values emitted by a source observable:
import { interval } from 'rxjs';
import { delay, share } from 'rxjs/operators';
const a = interval(500).pipe(share());
const b = a.pipe(delay(0), map(value => value + 10));
const c = b.pipe(delay(0), map(value => value + 100));
const d = a.pipe(delay(0), map(value => value + 1000));
I would like the values to be processed in a specific order, for example a, b, d, c, so b has priority over d, and d over c.
Use concat
const result = concat(a, b, d, c)
The inner merged observable isn't terminating with this code (rxjs 5.5.6):
let source = new Subject<string[]>();
// when the source emits a vector of strings, output
// each string with a 1s delay
source.switchMap(v => Observable.from(v)
.map(s => Observable.of(s).delay(1000).do(s => console.log('do: ' + s)))
// only one active observable at time
.mergeAll(1)
).subscribe(val => console.log('result: ' + val));
// emit two vectors, 1.5s apart
Observable.interval(1500).take(2).map(i => ['a' + i, 'b' + i, 'c' + i])
.subscribe(v => source.next(v));
Output is:
do: a0
result: a0
do: b0
do: a1
result: a1
do: c0
do: b1
result: b1
do: c1
result: c1
The expected output is:
do: a0
result: a0
do: a1
result: a1
do: b1
result: b1
do: c1
result: c1
That is, after the second vector emits, the switchMap should unsubscribe from the observable on the first vector, canceling that observable. And while the unsubscribe is clearly working, the inner observable is still running, as evidence by the "do: a0 .. b0 .. c0" in the output from the first example.
And in fact the expected output is exactly what you get from this code:
let source =
Observable.interval(1500).take(2).map(i => ['a' + i, 'b' + i, 'c' + i]);
source.switchMap(v => Observable.from(v)
.map(s => Observable.of(s).delay(1000).do(s => console.log('do: ' + s)))
.mergeAll(1)
).subscribe(val => console.log('result: ' + val));
But why doesn't the first example behave the same way?
If i use a range and limit the output with takeWhile:
Rx.Observable
.range( 1, 10 )
.do( idx => console.log('range: ', idx ))
.takeWhile( idx => idx <= 5 )
.subscribe( idx => console.log( 'res : ', idx ))
;
the output is:
range: 1
res : 1
range: 2
res : 2
range: 3
res : 3
range: 4
res : 4
range: 5
res : 5
range: 6
the produces values by range are not all consumed. 6 is pulled, does not pass the takeWhile, no more values are taken.
Now if i have a concatMap in between:
Rx.Observable
.range( 1, 10 )
.do( idx => console.log('range: ', idx ))
.concatMap( idx => {
var res = new Rx.Subject<number>();
setTimeout( () => {
res.next( idx );
res.complete();
}, 10 );
return res;
})
.takeWhile( idx => idx <= 5 )
.subscribe( idx => console.log( 'res: ', idx ))
;
The output is this:
range: 1
range: 2
range: 3
range: 4
range: 5
range: 6
range: 7
range: 8
range: 9
range: 10
res: 1
res: 2
res: 3
res: 4
res: 5
I would expect, that the values from range production would be limited here as well. concatMap preserves the order, so it makes only sense to pull the next value, when the previous observable is completed. But all range errors are pulled.
Is this a bug? Or what is the real behavior. Can you please help to understand.
Values produced by range() are buffered inside concatMap() operator and then pulled one by one. Then you're using setTimeout() to asynchronously emit values. Operators in general don't try to utilize any backpressure so all items from the source are emitted when they're ready.
Note that you could achieve the same even when using Observable.of() and the asynchronous scheduler Scheduler.async. This makes the emission from Observable.of to happen in a new event which makes it asynchronous.
const Observable = Rx.Observable;
const Scheduler = Rx.Scheduler;
Rx.Observable
.range( 1, 10 )
.do( idx => console.log('range: ', idx ))
.concatMap( idx => Observable.of(idx, Scheduler.async))
.takeWhile( idx => idx <= 5 )
.subscribe( idx => console.log( 'res: ', idx ));
See live demo: https://jsbin.com/xukolo/3/edit?js,console
Given a function that generates random numbers, how would you create an infinite Observable that produces random numbers at random intervals?
function getRandomNumber() {
// Assume this function returns a random number, e.g. 198
}
function getRandomDelay() {
// Assume this function returns a random delay in ms, e.g. 2000
}
Here is an example of a desired Observable:
---198--------64-------3---2----------18-------> (indefinitely)
3ms 7ms 6ms 3ms 10ms
As an alternative, if you don't want to live in a confusing timeout-world, you could write this entirely as a stream:
// the stream
const randomizer$ = Rx.Observable.of("")
.switchMap(() => Rx.Observable
.timer(getRandomDelay())
.mapTo(getRandomNumber()))
.repeat();
// subscribe to it
randomizer$.subscribe(num => console.log("Random number after random delay" + num));
// your utility functions
function getRandomNumber() {
return ~~(Math.random() * 200)
}
function getRandomDelay() {
return Math.random() * 1000
}
Working example here: http://jsbin.com/zipocaneya/edit?js,console
Alternative: Create the random number first and then add a delay (if the time of execution does not matter)
// the stream
const randomizer$ = Rx.Observable.of("")
.switchMap(() => Rx.Observable
.of(getRandomNumber())
.delay(getRandomDelay()
)
.repeat();
// subscribe to it
randomizer$.subscribe(num => console.log("Random number after random delay" + num));
Additional note: Since there is no concurrency or async-operation going on outside of the stream, instead of switchMap you could just as well use concatMap or flatMap - in this case they all work the same.
const { Observable } = require("rxjs");
const ob = new Observable(sub => {
let timeout = null;
// recursively send a random number to the subscriber
// after a random delay
(function push() {
timeout = setTimeout(
() => {
sub.next(getRandomNumber());
push();
},
getRandomDelay()
);
})();
// clear any pending timeout on teardown
return () => clearTimeout(timeout);
});
ob.subscribe(console.log);
Would you take a look at https://github.com/cszredwan/crazyObservable?
I am sure it gonna be of help, this is a custom observable to emit (randomly!) a fixed amount of data over a fixed time horizon.
The problem with your question is that randomly is being used with imprecise meaning. crazyObservable draws a single stream of data of all possibilities of emitting data over the time horizon specified. Of course, you can concat multiples instances of crazyObservable to get desired behavior.
In RxPY 3.0 you can use the following construction:
res = rx.generate(0, lambda x: True, lambda x: x + 1).pipe(
ops.map(lambda x: rx.timer(random.random() * 0.4).pipe(ops.map(lambda y: x))),
ops.merge(max_concurrent=1),
ops.map(lambda x: {'count': x, 'value': random.randint(0, 5)}))
This produces an infinite stream of random integers between 0 and 5 at random times with interarrival time uniformly distributed on [0, 0.4].
In RxPY 3.0, operations like switchmap or concatmap are not implemented (as in #olsn's reply). The concat_all operation can be achieved by merge with max_concurrent=1.
Edit:
rx.generate(0, lambda x: True, lambda x: x + 1)
is blocking. Using an infinite vanilla python generator such as
import itertools
r = rx.from_iterable(_ for _ in itertools.count(start=0, step=1))
is also blocking. You can add some scheduler e.g.
from rx.scheduler.eventloop import AsyncIOScheduler
from rx.scheduler import ThreadPoolScheduler
import multiprocessing
scheduler = AsyncIOScheduler(asyncio.get_event_loop())
# scheduler = ThreadPoolScheduler(multiprocessing.cpu_count()) # alternatively
res = rx.generate(0, lambda x: True, lambda x: x + 1).pipe(
ops.map(lambda x: rx.timer(random.random() * 0.4).pipe(ops.map(lambda y: x))),
ops.merge(max_concurrent=1),
ops.map(lambda x: {'count': x, 'value': random.randint(0, 5)}),
ops.subscribe_on(scheduler)
)