Clear an Rx.Observable bufferCount with an event driven Timeout? - javascript

I'm using rxjs 5.0:
How can I set a timeout, on this buffer. So that it will clear the bufferCount (11) when no keyup events happen for 5 seconds?
var keys = Rx.Observable.fromEvent(document, 'keyup');
var buffered = keys.bufferCount(11,1);
buffered.subscribe(x => console.log(x));

You can append a timeoutWith, which could return a fresh buffered after a certain timeout (5seconds in your case).
const keys$ = Rx.Observable.fromEvent(document, "keyup")
.map(ev => ev.keyCode|| ev.which); // this is just to have a readable output here in the SO-console
const buffered$ = keys$
.bufferCount(3,1) // replaced your 11 with 3 for easy demonstration
.timeoutWith(2000, Rx.Observable.defer(() => { // replaced 5 with 2 seconds (easier to test here)
console.log("New Buffer!");
return buffered$;
}));
buffered$.subscribe(console.log);
<script src="https://unpkg.com/rxjs/bundles/Rx.min.js"></script>
As an improvement, this could be even enhanced to only start the stream on the first stroke, otherwise we would have a constant timeout running (not critical, but could still be prevented).
const keys$ = Rx.Observable.fromEvent(document, "keyup")
.map(ev => ev.keyCode|| ev.which); // this is just to have a readable output here in the SO-console
const buffered$ = keys$
.take(1)
.switchMap(firstKey => {
console.log("New Buffer!");
return keys$
.startWith(firstKey)
.bufferCount(3,1) // replaced your 11 with 3 for easy demonstration
.timeoutWith(2000, Rx.Observable.defer(() => buffered$)); // replaced 5 with 2 seconds (easier to test here)
});
buffered$.subscribe(console.log);
<script src="https://unpkg.com/rxjs/bundles/Rx.min.js"></script>

I have another (and probably easier to understand) solution using window and switchMap():
var keys = Rx.Observable.fromEvent(document.getElementById('myinput'), 'keyup')
.map(event => event.keyCode)
.share();
var buffered = keys
.window(keys.debounceTime(5000))
.switchMap(observable => observable.bufferCount(5, 1))
.filter(buffer => buffer.length === 5);
buffered.subscribe(x => console.log(x));
See demo: https://jsbin.com/cakoru/17/edit?js,console,output
When you don't type for at least 5s the window() operator creates a new Observable that is subscribed internally in switchMap() and chained with a new .bufferCount() operator.

Here's how I'd do it:
const keys$ = Rx.Observable.fromEvent(document, 'keyup').map(ev => ev.keyCode|| ev.which);
keys$
.debounceTime(5000)
.startWith({})
.switchMap(x => keys$.bufferCount(11, 1))
.subscribe(x => console.log(x));
Here we've got a stream that yields a value each time typing stops for five seconds (kicked off with a dummy value) that switchMaps into a bufferCount.

Related

Conditional observable interaction RxJS

I have the following observables:
this.searchValue$ = this.searchValue.valueChanges.pipe(
startWith(''),
debounceTime(500)
);
this.cardAmount$ = fromEvent(this.button.nativeElement, 'click').pipe(
startWith(0),
scan(count => count + 20, 0)
);
To add a bit more context to the code: searchValue$ is related to input field changes and emits the value that is changed. cardAmount$ is related to button presses. Each time you press a button it emits a new value 20, 40, 60 and so on.
I would like "set" the value of cardAmount$ back to 0 once searchValue$ is emited. What is a correct RxJS way of doing this?
Sounds like the perfect case for the switchMap operator :
this.cardAmount$ = this.searchValue$
.pipe(switchMap( search =>
fromEvent(this.button.nativeElement, 'click')
.pipe(
startWith(0),
scan(count => count + 20, 0)
)));
A new Observable starting from 0 will be generated on each searchValue emission.
You can't with this code (as far as I know).
For that, you will need a proxy that acts both as an observable and an observer. Otherwise, you can't emit a value in your stream.
Try with BehaviorSubject :
this.searchValue$ = this.searchValue.valueChanges.pipe(
startWith(''),
debounceTime(500),
tap(() => this.cardAmount.next(0)),
);
this.cardAmount$ = new BehaviorSubject(0);
fromEvent(this.button.nativeElement, 'click').pipe(
startWith(0),
switchMap(() => this.cardAmount$),
).subscribe(curr => this.cardAmount$.next(curr + 20));
I slightly changed the last observer because if you don't and keep your previous one, the count value won't care about the reset of the value changes. To be sure it does care, you'll have to use the current value of the observer.

How can I apply timed back pressure in RxJS5?

Imagine I have the following code:
let a = Rx.Observable.of(1, 2, 3)
let b = Observable.zip(a, a, (a, b) => a + b)
b.forEach(t => console.log(t))
This immediately outputs the results. Now, how do I put a timed delay between each message as a way of back-pressure (note that I don't want a buffer; instead, I want a and b to become Cold Observables), like:
b.takeEvery(1000).forEach(t => console.log(t))
And have the exact same answer:
<wait 1s>
2
<wait 1s>
4
<wait 1s>
6
Alternative: If backpressure (ou pull mechanisms for some observables) is something not supported in RxJS, then how could one create an infinite generator without running out of resources?
Alternative 2: Other JS frameworks that support both pull and push mechanisms?
In case of RxJS 5.x back pressure is not support, but there is for example pausable operator in 4.x version. It works only with hot observables. More info on back pressure in case of 4.x and here (especially take a loot at the bottom and RxJS related description).
This Erik Meijer's tweet may be bit controversial but relevant: https://twitter.com/headinthebox/status/774635475071934464
For your own implementation of back pressure mechanism you need to have 2-way communication channel, which can be fairly easily created with 2 subjects - one for each end. Basically use next for sending messages and .subscribe for listing to the other end.
Creating a generator is doable as well - again using a subject to bridge between push- and pull-based worlds. Below an exemplary implementation for generating Fibonacci numbers.
const fib = () => {
const n = new Rx.Subject()
const f = n
.scan(c => ({ a: c.b, b: c.b + c.a }), { a: 0, b: 1 })
.map(c => c.a)
return {
$: f,
next: () => n.next()
}
}
const f = fib()
f.$.subscribe(n => document.querySelector('#r').innerHTML = n)
Rx.Observable.fromEvent(document.querySelector('#f'), 'click')
.do(f.next)
.subscribe()
<script src="https://cdnjs.cloudflare.com/ajax/libs/rxjs/5.5.6/Rx.js"></script>
<button id='f'>NEXT FIBONACCI</button>
<div id='r'>_?_<div>
Another js library which may be of interest for you is https://github.com/ubolonton/js-csp - did not use it, so not sure how it deals with back pressure.
the idea is to queue the time wait one after the other when the previous one finishes execution Fiddle
let a = Rx.Observable.of(1, 2, 3);
let b = Rx.Observable.zip(a, a, (a, b) => a + b);
// getting values into array
var x = [];
b.forEach(t => x.push(t));
var takeEvery = function(msec,items,action,index=0){
if(typeof(action) == "function")
if(index<items.length)
setTimeout(
function(item,ind){
action(item);
takeEvery(msec,items,action,ind);
},msec, items[index],++index);
};
// queueing over time
takeEvery(1000,x, function(item){
console.log(item);
});

Observable delay after long processing of each element of large array

Say we have a big array and processing of each element in that array takes a long time (5s). We want to add some delay (2s) before processing of next element.
I've somewhat managed to achieve that behavior like that:
let arr = [1, 2, 3]
let i = 0
Rx.Observable.of(null)
.map(val => arr[i++])
.do(val => {
console.log(val + ' preloop')
let time = +new Date() + 5000; while (+new Date() < time) {}
console.log(val + ' afterloop')
})
.delay(2000)
.flatMap(val => (i < arr.length) ? Rx.Observable.throw(null) : Rx.Observable.empty())
.retry()
.subscribe(console.log, console.log, () => console.log('completed'))
The output is as expected:
1 preloop
delay 5s
1 afterloop
delay 2s
2 preloop
...
completed
But this code is ugly, not reusable and buggy and doesn't comply with the philosophy of rx. What is the better way?
Note that the array (or it might even be not an array at all) is big and https://stackoverflow.com/a/21663671/2277240 won't work here.
The question is hypothetycal though I can think of some use cases.
I'm not sure why the fact that the array is big important here, and you can solve your issue using the same method from the link you provided.
For your long operation a better practice would probably be a promise. I used an async sleep function to simulate a 5 second operation, you can replace it with your promise.
The trick for the extra delay, is to concat a dummy element, delay it and then ignore it.
const sleep = ms => new Promise(resolve => setTimeout(resolve, ms));
Rx.Observable.from([1, 2, 3])
.concatMap(item =>
Rx.Observable.defer(() => sleep(5000))
.mapTo(item)
.concat(Rx.Observable.of(null).delay(2000).ignoreElements())
)
.subscribe(console.log, console.log, () => console.log('completed'))
<script src="https://cdnjs.cloudflare.com/ajax/libs/rxjs/5.5.2/Rx.js"></script>
An alternative for delay which does not delay the first emit,
source
.first()
.merge(
source.skip(1).delay(2000)
)

RxJS: Debounce with regular sampling

I have an Observable that emits a stream of values from user input (offset values of a slider).
I want to debounce that stream, so while the user is busy sliding, I only emit a value if nothing has come through for, say 100ms, to avoid being flooded with values. But then I also want to emit a value every 1 second if it is just endlessly debouncing (user is sliding back and forth continuously). Once the user stops sliding though, I just want the final value from the debounced stream.
So I want to combine the debounce with a regular "sampling" of the stream. Right now my setup is something like this:
const debounce$ = slider$.debounceTime(100),
sampler$ = slider$.auditTime(1000);
debounce$
.merge(sampler$)
.subscribe((value) => console.log(value));
Assuming the user moves the slider for 2.4 seconds, this emits values as follows:
start end
(x)---------|---------|---(x)|----|
| | | |
1.0 2.0 2.5 3.0 <-- unwanted value at the end
^ ^ ^
sample sample debounce <-- these are all good
I don't want that extra value emitted at 3 seconds (from the sampler$ stream).
Obviously merge is the wrong way to combine these two streams, but I can't figure out what combination of switch, race, window or whatever to use here.
You can solve the problem by composing an observable that serves as a signal, indicating whether or not the user is currently sliding. This should do it:
const sliding$ = slider$.mapTo(true).merge(debounce$.mapTo(false));
And you can use that to control whether or not the sampler$ emits a value.
A working example:
const since = Date.now();
const slider$ = new Rx.Subject();
const debounce$ = slider$.debounceTime(100);
const sliding$ = slider$.mapTo(true).merge(debounce$.mapTo(false));
const sampler$ = slider$
.auditTime(1000)
.withLatestFrom(sliding$)
.filter(([value, sliding]) => sliding)
.map(([value]) => value);
debounce$
.merge(sampler$)
.subscribe(value => console.log(`${time()}: ${value}`));
// Simulate sliding:
let value = 0;
for (let i = 0; i <= 2400; i += 10) {
value += Math.random() > 0.5 ? 1 : -1;
slide(value, i);
}
function slide(value, at) {
setTimeout(() => slider$.next(value), at);
}
function time() {
return `T+${((Date.now() - since) / 1000).toFixed(3)}`;
}
.as-console-wrapper { max-height: 100% !important; top: 0; }
<script src="https://unpkg.com/rxjs#5/bundles/Rx.min.js"></script>
For those who are interested, this is the approach I took, inspired by #cartant's answer.
const slider$ = new Rx.Subject();
const nothing$ = Rx.Observable.never();
const debounce$ = slider$.debounceTime(100);
const sliding$ = slider$.mapTo(true)
.merge(debounce$.mapTo(false))
.distinctUntilChanged();
const sampler$ = sliding$
.switchMap((active) => active ? slider$.auditTime(1000) : nothing$);
debounce$
.merge(sampler$)
.subscribe(value => console.log(`${time()}: ${value}`));
The difference is adding distinctUntilChanged on the sliding$ stream to only get the on/off changes, and then doing a switchMap on that to either have the sampler return values or not.

Is-there an opposite of the `race` operator in RxJS?

I have two observables and I want listen to the one that emits its first value last, is there an operator for this ? Something like that :
let obs1 = Rx.Observable.timer(500,500);
let obs2 = Rx.Observable.timer(1000,1000); // I want the values from this one
let sloth = Rx.Observable.sloth(obs1,obs2);
where the sloth observable would emit the values from obs2 as it is the one who emits its first value last.
If that's not the case, is there any other way ?
I see this possibility, for now, but I'm curious if someone find anything else :
let obs1 = Rx.Observable.timer(500,500).map(i=>`cheetah ${i}`);
let obs2 = Rx.Observable.timer(1000,1000).map(i=>`sloth ${i}`);
let sloth = Rx.Observable.merge(
obs1.take(1).mapTo(obs1),
obs2.take(1).mapTo(obs2)
).takeLast(1).mergeAll()
sloth.subscribe(data=>console.log(data))
<script src="https://unpkg.com/#reactivex/rxjs#5.3.0/dist/global/Rx.js"></script>
Edit as pointed out by #user3743222 (very nice nickname :-D ), it would not work for hot observables, but this should be fine :
let obs1 = Rx.Observable.timer(500,500).map(i=>`cheetah ${i}`).publish();
let obs2 = Rx.Observable.timer(1000,1000).map(i=>`sloth ${i}`).publish();
obs1.connect();
obs2.connect();
let sloth = Rx.Observable.merge(
obs1.take(1).map((val)=>obs1.startWith(val)),
obs2.take(1).map((val)=>obs2.startWith(val))
).takeLast(1).mergeAll();
sloth.subscribe(data=>console.log(data));
<script src="https://unpkg.com/#reactivex/rxjs#5.3.0/dist/global/Rx.js"></script>
I like your solution (though I suspect you might never see the first emitted value if you have a hot stream - if the source is cold, all seems good). Can you make a jsfiddle to check that out? If you dont miss any value, your solution is the best. If you do, it might be possible to correct it by adding the skipped value back to the source (obs1.take(1).map(val => obs1.startWith(val)).
Otherwise, for a generic lengthy solution, the key here is that you have state, so you need also the scan operator. We tag the source with an index, and we keep a state which represents the indices of the sources which already have started. When all but one have started, we know the index of the one who hasnt, and we pick only the values from that one. Please note, that this should work independently of whether the sources are hot or cold as all is made in one pass, i,e, there is no multiple subscriptions.
Rx.Observable.merge(
obs1.map(val => {val, sourceId: 1})
obs2.map(val => {val, sourceId: 2})
obsn.map(val => {val, sourceId: n})
).scan(
(acc, valueStruct) => {
acc.valueStruct = valueStruct
acc.alreadyEmitted[valueStruct.sourceId - 1] = true
if (acc.alreadyEmitted.filter(Boolean).length === n - 1) {
acc.lastSourceId = 1 + acc.alreadyEmitted.findIndex(element => element === false)
}
return acc
}, {alreadyEmitted : new Array(n).fill(false), lastSourceId : 0, valueStruct: null}
)
.map (acc => acc.valueStruct.sourceId === acc.lastSourceId ? acc.valueStruct.val : null )
.filter(Boolean)
Maybe there is shorter, I dont know. I'll try to put that in a fiddle to see if it actually works, or if you do before let me know.
How about this?
let obs1 = Rx.Observable.timer(500,500);
let obs2 = Rx.Observable.timer(1000,1000);
let sloth = Rx.Observable.race(
obs1.take(1).concat(obs2),
obs2.take(1).concat(obs1)
).skip(1);
And as a function with multiple Observables support:
let sloth = (...observables) =>
observables.length === 1 ?
observables[0] :
observables.length === 2 ?
Rx.Observable.race(
observables[0].take(1).concat(observables[1]),
observables[1].take(1).concat(observables[0])
).skip(1) :
observables.reduce((prev, current) => sloth(prev, current))[0];
I had the same issue and was able to solve it using a combination of merge and skipUntil. The pipe(last()) stops you receiving multiple results if both complete at the same time.
Try pasting the following into https://rxviz.com/:
const { timer, merge } = Rx;
const { mapTo, skipUntil, last } = RxOperators;
let obs1 = timer(500).pipe(mapTo('1'));
let obs2 = timer(1000).pipe(mapTo('2')); // I want the values from this one
let sloth = merge(
obs1.pipe(skipUntil(obs2)),
obs2.pipe(skipUntil(obs1))
).pipe(last())
sloth
Using RxJS 6 and ReplaySubject:
function lastOf(...observables) {
const replayable = observables
.map(o => {
let r = o.pipe(multicast(new ReplaySubject(1)));
r.connect();
return r;
});
const racing = replayable
.map((v, i) => v.pipe(
take(1),
mapTo(i),
))
;
return of(...racing).pipe(
mergeAll(),
reduce((_, val) => val),
switchMap(i => replayable[i]),
);
}
Use:
const fast = interval(500);
const medium = interval(1000);
const slow = interval(2000);
lastOf(fast, slow, medium).subscribe(console.log);

Categories

Resources