According to the documentation, the buffer transform will wait for a delay before emitting any values. What I'd like is to get the current value immediately, then only update every X seconds.
I've not been able to achieve this with rxjs yet. The closest I've come is to bind the observable then use a setTimeout function to rebind after the buffer timeout occurs. This has a side effect of clearing the current value for those X seconds before emitting the current values.
Any ideas?
Thanks!
Assuming by "current value immediately" you mean "first value as soon as it emits", you can buffer on the second element to the last, and merge in the first:
// source$: Observable<T>
const pub_source$ = source$.publish();
Observable.merge(
pub_source$.take(1).map(first => [first]),
pub_source$.skip(1).buffer(Observable.interval(X))
);
pub_source$.connect();
The source needs to be cold so that take(1) and skip(1) relate to the same element, so we use publish. The first element is also wrapped to keep the output type T[] consistent.
There's an easier way using the zip operator, see lightbulb note in learnrxjs
Combined with interval or timer, zip can be used to time output from another source!
// Useful for slow source that emits at around the same rate as interval
// but suffers back-pressure with fast-emitting source
const interval = 1000
const output = Observable.zip(source, Observable.timer(0, interval))
.map(x => x[0])
The Observable.timer 'regulates' the output from source. Note, timer's first parameter sets the delay for the first emit.
Working example: CodePen
Footnote
I just realized this will create back-pressure (build-up of un-emitted values) if you have a lot of events per second, so buffer is the better way to go with a fast emitting source.
// Buffered version for fast source
const output2 = source.buffer(Observable.timer(0, interval))
.filter(x => x.length) // filter out empty buffer emits
.flatMap(x => Observable.from(x)) // optional, converts array back to single emits
Related
I'm trying to achieve something very similar to a buffer count. As values come through the pipe, bufferCount of course buffers them and sends them down in batches. I'd like something similar to this that will emit all remaining items if there are currently fewer than the buffer size in the stream.
It's a little confusing to word, so I'll provide an example with what I'm trying to achieve.
I have something adding items individually to a subject. Sometimes it'll add 1 item a minute, sometimes it'll add 1000 items in 1 second. I wish to do a long running process (2 seconds~) on batches of these items as to not overload the server.
So for example, consider the timeline where P is processing
---A-----------B----------C---D--EFGHI------------------
|_( P(A) ) |_(P(B)) |_( P(C) ) |_(P([D, E, F, G, H, I]))
This way I can process the events in small or large batches depending on how many events are coming through, but i ensure the batches remain smaller than X.
I basically need to map all the individual emits into emits that contain chunks of 5 or fewer. As I pipe the events into a concatMap, events will start to stack up. I want to pick these stacked up events off in batches. How can I achieve this?
Here's a stackblitz with what I've got so far: https://stackblitz.com/edit/rxjs-iqwcbh?file=index.ts
Note how item 4 and 5 don't process until more come in and fill in the buffer. Ideally after 1,2,3 are processed, it'll pick off 4,5 the queue. Then when 6,7,8 come in, it'll process those.
EDIT: today I learned that bufferTime has a maxBufferSize parameter, that will emit when the buffer reaches that size. Therefore, the original answer below isn't necessary, we can simply do this:
const stream$ = subject$.pipe(
bufferTime(2000, null, 3), // <-- buffer emits # 2000ms OR when 3 items collected
filter(arr => !!arr.length)
);
StackBlitz
ORIGINAL:
It sounds like you want a combination of bufferCount and bufferTime. In other words: "release the buffer when it reaches size X or after Y time has passed".
We can use the race operator, along with those other two to create an observable that emits when the buffer reaches the desired size OR after the duration has passed. We'll also need a little help from take and repeat:
const chunk$ = subject$.pipe(bufferCount(3));
const partial$ = subject$.pipe(
bufferTime(2000),
filter(arr => !!arr.length) // don't emit empty array
);
const stream$ = race([chunk$, partial$]).pipe(
take(1),
repeat()
);
Here we define stream$ to be the first to emit between chunk$ and partial$. However, race will only use the first source that emits, so we use take(1) and repeat to sort of "reset the race".
Then you can do your work with concatMap like this:
stream$.pipe(
concatMap(chunk => this.doWorkWithChunk(chunk))
);
Here's a working StackBlitz demo.
You may want to roll it into a custom operator, so you can simply do something like this:
const stream$ = subject$.pipe(
bufferCountTime(5, 2000)
);
The definition of bufferCountTime() could look like this:
function bufferCountTime<T>(count: number, time: number) {
return (source$: Observable<T>) => {
const chunk$ = source$.pipe(bufferCount(count));
const partial$ = source$.pipe(
bufferTime(time),
filter((arr: T[]) => !!arr.length)
);
return race([chunk$, partial$]).pipe(
take(1),
repeat()
);
}
}
Another StackBlitz sample.
Since I noticed the use of forkJoin in your sample code, I can see you are sending a request to the server for each emission (I was originally under the impression that you were making only 1 call per batch with combined data).
In the case of sending one request per item the solution is much simpler!
There is no need to batch the emissions, you can simply use mergeMap and specify its concurrency parameter. This will limit the number of currently executing requests:
const stream$ = subject$.pipe(
mergeMap(val => doWork(val), 3), // 3 max concurrent requests
);
Here is a visual of what the output would look like when the subject rapidly emits:
Notice the work only starts for the first 3 items initially. Emissions after that are queued up and processed as the prior in flight items complete.
Here's a StackBlitz example of this behavior.
TLDR;
A StackBlitz app with the solution can be found here.
Explanation
Here would be an approach:
const bufferLen = 3;
const count$ = subject.pipe(filter((_, idx) => (idx + 1) % bufferLen === 0));
const timeout$ = subject.pipe(
filter((_, idx) => idx === 0),
switchMapTo(timer(0))
);
subject
.pipe(
buffer(
merge(count$, timeout$).pipe(
take(1),
repeat()
)
),
concatMap(buffer => forkJoin(buffer.map(doWork)))
)
.subscribe(/* console.warn */);
/* Output:
Processing 1
Processing 2
Processing 3
Processed 1
Processed 2
Processed 3
Processing 4
Processing 5
Processed 4
Processed 5
Processing 6 <- after the `setTimeout`'s timer expires
Processing 7
Processing 8
Processed 6
Processed 7
Processed 8
*/
The idea was to still use the bufferCount's behavior when items come in synchronously, but, at the same time, detect when fewer items than the chosen bufferLen are in the buffer. I thought that this detection could be done using a timer(0), because it internally schedules a macrotask, so it is ensured that items emitted synchronously will be considered first.
However, there is no operator that exactly combines the logic delineated above. But it's important to keep in mind that we certainly want a behavior similar to the one the buffer operator provides. As in, we will for sure have something like subject.pipe(buffer(...)).
Let's see how we can achieve something similar to what bufferTime does, but without using bufferTime:
const bufferLen = 3;
const count$ = subject.pipe(filter((_, idx) => (idx + 1) % bufferLen === 0));
Given the above snippet, using buffer(count$) and bufferTime(3), we should get the same behavior.
Let's move now onto the detection part:
const timeout$ = subject.pipe(
filter((_, idx) => idx === 0),
switchMapTo(timer(0))
);
What it essentially does is to start a timer after the subject has emitted its first item. This will make more sense when we have more context:
subject
.pipe(
buffer(
merge(count$, timeout$).pipe(
take(1),
repeat()
)
),
concatMap(buffer => forkJoin(buffer.map(doWork)))
)
.subscribe(/* console.warn */);
By using merge(count$, timeout$), this is what we'd be saying: when the subject emits, start adding items to the buffer and, at the same time, start the timer. The timer is started too because it is used to determine if fewer items will be in the buffer.
Let's walk through the example provided in the StackBlitz app:
from([1, 2, 3, 4, 5])
.pipe(tap(i => subject.next(i)))
.subscribe();
// Then mimic some more items coming through a while later
setTimeout(() => {
subject.next(6);
subject.next(7);
subject.next(8);
}, 10000);
When 1 is emitted, it will be added to the buffer and the timer will start. Then 2 and 3 arrive immediately, so the accumulated values will be emitted.
Because we're also using take(1) and repeat(), the process will restart. Now, when 4 is emitted, it will be added to the buffer and the timer will start again. 5 arrives immediately, but the number of the collected items until now is less than the given buffer length, meaning that until the 3rd value arrives, the timer will have time to finish. When the timer finishes, the [4,5] chunk will be emitted. What happens with [6, 7, 8] is the same as what happened with [1, 2, 3].
I have an Observable, source, that may emit items at unpredictable times. I'm trying to use it to build another Observable that reliably emits its values every 500ms.
Let's say that source emits values at these times:
100ms - first item
980ms - second item
1020ms - third item
1300ms - fourth item, etc.
I'd like to "smooth" this stream, so that I get outputs like:
500ms - first item
1000ms - second item
1500ms - third item
2000ms - fourth item
A naive approach might be to just add a delay in between emissions of source items. But, that won't create evenly spaced intervals, like I want.
I've tried various combinations of .timer(), .interval(), and .flatMap(), but nothing promising, yet.
I think you could try this:
const src$ = merge(
timer(100).pipe(mapTo(1)),
timer(980).pipe(mapTo(2)),
timer(1020).pipe(mapTo(3)),
timer(1300).pipe(mapTo(4))
);
src$
.pipe(
bufferTime(500),
mergeAll()
)
.subscribe(console.log);
bufferTime is used in order to create a timer that will emit at constant intervals, irrespective of the emitted values. Then mergeAll is used to explode the array resulted from bufferTime.
StackBlitz demo.
For a source emitting faster than your interval
zip your source with an interval of the required time span.
zip(source, interval(500)).pipe(
map(([value, _]) => value) // only emit the source value
)
zip emits the 1st item from source with the 1st item from interval, then the 2nd item from source with the 2nd item from interval and so on. If the output observable should only emit when interval emits, the Nth value from source has to arrive before the Nth value from interval.
Potential Problem:
If your source emits slower than interval at some point (i.e. the Nth value from source arrives after the Nth value from interval) then zip will emit directly without waiting for the next time interval emits.
// the 5th/6th value from source arrive after the 5th/6th value from interval
v v
source: -1--------2-3---4---------------5----6-----
interval: -----1-----2-----3-----4-----5-----6-----7-
zip output: -----1-----2-----3-----4--------5----6-----
✓ ✓ ✓ ✓ ⚠️ ⚠️
// emits 5 and 6 don't happen when interval emits
For a source emitting at any rate
function emitOnInterval<T>(period: number): MonoTypeOperatorFunction<T> {
return (source: Observable<T>) =>
defer(() => {
let sourceCompleted = false;
const queue = source.pipe(
tap({ complete: () => (sourceCompleted = true) }),
scan((acc, curr) => (acc.push(curr), acc), []) // collect all values in a buffer
);
return interval(period).pipe(
withLatestFrom(queue), // combine with the latest buffer
takeWhile(([_, buffer]) => !sourceCompleted || buffer.length > 0), // complete when the source completed and the buffer is empty
filter(([_, buffer]) => buffer.length > 0), // only emit if there is at least on value in the buffer
map(([_, buffer]) => buffer.shift()) // take the first value from the buffer
);
});
}
source.pipe(
emitOnInterval(500)
)
// the 5th/6th value from source arrive after the 5th/6th value from interval
v v
source: -1--------2-3---4---------------5----6-----
interval: -----1-----2-----3-----4-----5-----6-----7-
output: -----1-----2-----3-----4-----------5-----6-
✓ ✓ ✓ ✓ ✓ ✓
// all output emits happen when interval emits
https://stackblitz.com/edit/rxjs-qdlktm?file=index.ts
You can use a combination of combineLatest, interval and throttle - you add a second observable, interval with the time between calls you want (e.g. 500ms), so every 500ms your observable will emit (when used with combineLatest), now it will emit the values every 500ms and every time the original source emits, so you can add throttle in a pipe and that will cause the interval to throttle:
combineLatest([source, timer(5000)])
.pipe(
throttle(() => interval(5000)),
tap(([value]) => {
console.log("emitted", value, new Date().getSeconds());
})
)
.subscribe();
(tap is not required here, just added to demonstrate)
I have this sample code:
interval(500).pipe(
throttleTime(1000)
).subscribe(arg => {
console.log(arg);
});
Which emits:
0
3
6
...
I understand that it emits the latest value every 1000 milliseconds. My problem is that it ignores the values that aren't the latest. Is there an operator similar to throttleTime, but one that saves these ignored values? I'd like it to emit:
[0]
[1,2,3]
[4,5,6]
...
Edit: Ideally, I'd like something that listens to clicks of a button. When the first click happens, the code starts a timer for N milliseconds. The user can keep clicking during this time. Once N milliseconds are up, the operator fires with an array holding all of the events that happened during those N milliseconds.
Super-ideally, I'd like the timer to reset everytime the user clicks the button.
You can use bufferToggle. It collects values and returns in an array, just as per your requirement:
const click$ = fromEvent(document, 'click').pipe(
// count emitions
scan(acc => acc += 1, 0)
)
const timerInterval = () => timer(5000);
// buffer emitions to an array. When first click happens 'turn on' buffer
// and when timer ends turn it off.
// throttle used to avoid turning on buffer on every click
click$.pipe(
bufferToggle(
click$.pipe(throttle(timerInterval)),
timerInterval
),
)
.subscribe(console.log)
But to note - there is no clear separation between clicking intervals. For ex., user might be clicking longer than 5 secs and in result, two emitions will happen.
But this is more as an architectural task for you to solve.
DEMO
As title suggest, both function seems to have similar effect and return emit nothing when predicate does not match. It looks like skipWhile is the reverse of filter ?
As #cartant says
skipWhile "... emits all further source items as soon as the condition becomes false"
Note that skipWhile takes a predicate (expression that returns a boolean).
In addition there is also skipUntil, which this takes an observable and values will be skipped until that observable emits anything.
So sometimes the following can be useful (pan is a hammerjs event btw.):
// When event.deltaX reaches 20 emit true to indicate a threshold is reached
const thresholdReached$ = pan$.pipe(map(e => Math.abs(e.deltaX) > 20 ), first(v => v), shareReplay(1));
// Emit all events, but skip them until the first time that thresholdReached$ emits
const panMove$ = pan$.pipe(skipUntil(thresholdReached$));
Important to realize that the observable passed to skipUntil can emit any value to trigger the skipping to stop. It doesn't have to be a 'truthy' value.
So if you have var b = new BehaviorSubject(false) then skipUntil(b) will immediately stop skipping and you will quickly get very confused!
At the moment zip will only produce a value whenever all of the zipped observable produces a value. E.g. from the docs:
Merges the specified observable sequences or Promises into one
observable sequence by using the selector function whenever all of the
observable sequences have produced an element
I'm looking for an observable which can sort of zip an observable but will produce an array of sequence of the zipped observable wherein it doesn't matter if all produces a value..
e.g. lets say i have tick$, observ1, observ2.. tick$ always produce value every x secs.. while observ1 and observ2 only produces from time to time..
I'm expecting my stream to look like
[tick, undefined, observ2Res],
[tick, undefined, undefined],
[tick, observ1Res, observ2Res]
...
...
its not combine latest, given that combine latest takes the latest value of a given observable.
I believe buffer (or maybe sample) might get you on the right track. The buffer method accepts an Observable that's used to define our buffer boundaries. The resulting stream emits any items that were emitted in that window (example stolen from RXJS docs for buffer):
var source = Rx.Observable.timer(0, 50)
.buffer(function () { return Rx.Observable.timer(125); })
.take(3);
var subscription = source.subscribe(x => console.log('Next: ', x));
// => Next: 0,1,2
// => Next: 3,4,5
// => Next: 6,7
So we now have a way to get all of a stream's emitted events in a certain time window. In your case, we can use tick$ to describe our sampling period and observ1 and observ2 are our underlying streams that we want to buffer:
const buffered1 = observ1.buffer(tick$);
const buffered2 = observ2.buffer(tick$);
Each of these streams will emit once every tick$ period, and will emit a list of all emitted items from the underlying stream (during that period). The buffered stream will emit data like this:
|--[]--[]--[1, 2, 3]--[]-->
To get the output you desire, we can choose to only look at the latest emitted item of each buffered result, and if there's no emitted data, we can pass null:
const buffered1 = observ1.buffer($tick).map(latest);
const buffered2 = observ2.buffer($tick).map(latest);
function latest(x) {
return x.length === 0 ? null : x[x.length - 1];
}
The previous sample stream I illustrated will now look like this:
|--null--null--3--null-->
And finally, we can zip these two streams to get "latest" emitted data during our tick$ interval:
const sampled$ = buffered1.zip(buffered2);
This sampled$ stream will emit the latest data from our observ1 and observ2 streams over the tick$ window. Here's a sample result:
|--[null, null]--[null, 1]--[1, 2]-->