Creating a timer if not already available - RXJS - rxjs

I want to create a list of items which are coming up in a particular time period. When the first item comes up, I want to start the timer, and, till the timer completes, any further items coming has to be added to the same list. Once the timer time completes, need to get this total list. After this, again another item comes, it has to start with a new list.
How do I do this in RxJS?

If I understood you correctly, you can use a bufferToggle() and a BehaviorSubject.
Note that I multicasted the randomIntGenerator, which would be your asynchronous list of items.
You can manipulate the predicatei > 5 suitable to your context
The BehaviorSubject's state is used to ensure that bufferToggle() is only allowed to track ONE stream of buffer at any time.
var Rx = require('rxjs');
let tap = new Rx.BehaviorSubject(false);
tap.subscribe(i=>console.log(`tap...${(i? 'on' : 'off')}`))
let randomIntGenerator = Rx.Observable
.interval(1000)
.map( ()=>getRandomInt(1 , 10) )
.share()
randomIntGenerator
.do(i=>console.log('emits: ', i))
.bufferToggle(
randomIntGenerator,
(i)=>{
if(i>5 && !tap.value){
tap.next(true)
return Rx.Observable.interval(3000)
}
}
)
.do(()=>tap.next(false) )
.subscribe( list=>console.log('#newlist -->', list) )
function getRandomInt (min, max){
//From MDN's Math,random() example
min = Math.ceil(min);
max = Math.floor(max);
return Math.floor(Math.random() * (max - min)) + min;
}
See the live implementation here

This code will do it for you:
console.clear()
const getRandom = () => {
return Math.round(Math.random()*300,0)
}
const timer$ = Rx.Observable.interval(1000) // A stream of numbers
.map(getRandom)
// .do(console.log) // uncomment to see each number as it is generated
.bufferTime(5000)
const s = timer$.subscribe(val => console.log( val));
This one uses a timer and a random number generator to provide a series of random numbers. These are buffered up for 5 seconds and then returned as an array.
The process starts again a second later as the next number arrives
A codepen is here https://codepen.io/mikkel/pen/VyzEgb?editors=1001

Related

How make an observable buffer data till an event happen and stop buffering?

I have outward stream of drawing from an user and I need wait till data of the user profile come.
So in general I want buffer data till an event happens and then play this data and skip buffering.
I can do it with external array like the follow code show (stackblitz):
import { of, interval } from 'rxjs';
import { map, take } from 'rxjs/operators';
const numbers = interval(1000);
const source = numbers.pipe(
take(4)
);
let allowPerform = false;
setTimeout(_=>{allowPerform = true},2001);
const fifo=[];
source.subscribe(x => {
fifo.push(x);
if (!allowPerform) {
console.log('skip perform for:', x);
return;
}
let item ;
while ( fifo.length) {
item = fifo.shift();
console.log('perform for:', item);
}
});
it's output:
skip perform for: 0
skip perform for: 1
perform for: 0
perform for: 1
perform for: 2
perform for: 3
However how to do it in RXjs's way?
Here could be a way to do this:
// Buffer until the `notifier` emits
// Then do not buffer anymore, just send the values
const src$ = interval(1000).pipe(take(10), publish(), refCount());
const notifier$ = timer(2001);
concat(
src$.pipe(buffer(notifier$), mergeAll()),
src$
).subscribe(console.log, null, () => console.warn('complete'));
Using publish(), refCount() will multicast the emitted values to all the consumers. This is achieved by placing a Subject between the source and the data consumers, meaning that the source won't be subscribed multiple times.
src$.pipe(buffer(notifier$), mergeAll()), will buffer until notifier$ emits. But, because notifier$ also completes, the entire observable passed will complete, allowing for the next observable(src$) to be subscribed.
mergeAll() is used because buffer will emit an array of the collected values and with mergeAll() we can get the values separately.
StackBlitz

Filtering a BehaviorSubject

I have a BehaviorSubject that I'd like to be able to filter, but maintain it's behavior-subject-like quality that new subscribers always get a value when they subscribe, even if the last value emitted was filtered out. Is there a succinct way to do that using built-in functions from rxjs? For example:
const isEven = (n) => n % 2 === 0;
const source = new BehaviorSubject(1);
const stream = source.pipe(filter(isEven));
stream.subscribe((n) => console.log(n)); // <- I want this to print `1`
source.next(2); // prints `2`; that's good
source.next(3); // does not print anything; that's good
I've written my own implementation, but would prefer a simpler solution using existing operators instead if it's easy.
Just use a second BehaviorSubject
const { BehaviorSubject } = rxjs;
const { filter} = rxjs.operators;
const isEven = (n) => n % 2 === 0;
const source = new BehaviorSubject(1);
const stream = new BehaviorSubject(source.getValue());
source.pipe(filter(isEven)).subscribe(stream);
stream.subscribe(val => { console.log(val); });
source.next(2);
source.next(3);
<script src="https://cdnjs.cloudflare.com/ajax/libs/rxjs/6.4.0/rxjs.umd.min.js"></script>
Adrian's answer gets the credit, it looks like he answer the best way given the built-in operators available with rxjs itself. It didn't quite meet my needs, so I published my custom operator in my little library s-rxjs-utils. It it called filterBehavior(). From the docs:
Works like filter(), but always lets through the first emission for each new subscriber. This makes it suitable for subscribers that expect the observable to behave like a BehaviorSubject, where the first emission is processed synchronously during the call to subscribe() (such as the async pipe in an Angular template).
Your stream has already been piped to use the isEven filter, so your initial value of 1 is not shown in your console is behaving as expected.
If you want to see your initial value of 1, subscribe directly to the BehaviourSubject:
const isEven = (n) => n % 2 === 0;
const source = new BehaviorSubject(1);
const stream = source.pipe(filter(isEven));
// should print 1, and should print 2 and 3 when your source is nexted.
source.subscribe((n) => console.log(n));
stream.subscribe((n) => console.log(n)); // <- should NOT Print 1, because it has been filtered
source.next(2); // prints `2`; that's good
source.next(3); // does not print anything; that's good

Resolve array of observables and append in final array

I have an endpoint url like http://site/api/myquery?start=&limit= which returns an array of strings.
If I call this endpoint in this way, the server hangs since the array of strings length is huge.
I need to generate an an array of observables with incremental "start" and "limit" parameters, resolve all of then either in sequence or in parallel, and then get a final observable which at the end yields the true array of strings, obtained merging all the subarray of strings returned by the inner observables.
How should I do that?
i.e. the array of observables would be something like
[
httpClient.get(http://site/api/myquery?start=0&limit=1000),
httpClient.get(http://site/api/myquery?start=1000&limit=1000),
httpClient.get(http://site/api/myquery?start=2000&limit=1000),
....
]
If you know the length before making all these queries — then you can create as many http-get Observables as you need, and then forkJoin them using projection fn.
forkJoin will let you make parallel queries and then merge results of those queries. Heres an example:
import { forkJoin } from 'rxjs';
// given we know the length:
const LENGTH = 500;
// we can pick arbitrary page size
const PAGE_SIZE = 50;
// calculate requests count
const requestsCount = Math.ceil(LENGTH / 50);
// generate calculated number of requests
const requests = (new Array(requestsCount))
.fill(void 0)
.map((_,i) => {
const start = i * PAGE_SIZE;
return http.get(`http://site/api/myquery?start=${start}&limit=${PAGE_SIZE}`);
});
forkJoin(
requests,
// projecting fn
// merge all arrays into one
// suboptimal merging, just for example
(...results) => results.reduce(((acc, curr)=> [...acc, ...curr]) , [])
).subscribe(array => {
console.log(array);
})
Check this forkJoin example for reference.
Hope this helps
In the case that you do not know the total number of items, you can do this using expand.
The following article gives a good introduction to expand and an explanation of how to use it for pagination.
https://ncjamieson.com/understanding-expand/
Something along the lines of the code below would work in your case, making the requests for each page in series.
const limit = 1000;
let currentStart = 0;
let getUrl = (start, limit) => `http://site/api/myquery?start=${start}&limit=${limit}`;
httpClient.get(getUrl(currentStart, limit)).pipe(
expand(itemsArray => {
if (itemsArray.length) {
currentStart += limit;
return httpClient.get(getUrl(currentStart, limit));
}
return empty();
}),
reduce((acc, value) => [...acc, ...value]),
).subscribe(itemsArray => {
console.log(itemsArray);
})
This will log out the final array of items once the entire series of requests has been resolved.

concatMap() equivalent but async like in mergeMap()

I have an observable myObservable :
let myObservable = Observable.of(2000, 1000)
With concatMap() : TOTAL TIME = 3000 millis, results in the original order.
myObservable.concatMap(v => Rx.Observable.of(v).delay(v))
// concatMap: 2000, concatMap: 1000
With mergeMap() : TOTAL TIME = 2000 millis, results not in the original order.
myObservable.mergeMap(v => Rx.Observable.of(v).delay(v))
// mergeMap: 1000, mergeMap: 2000
I want a way to get the results in the original order like in concatMap, but calling each nested observable asynchronously instead of waiting for the next nested observable to complete :
// --- The behavior that I want ---
myObservable.myCustomMap(v => Rx.Observable.of(v).delay(v))
// myCustomMap: 2000, myCustomMap: 1000
// TOTAL TIME = 2000 millis
Is there an elegant solution?
Edit : I am looking for a solution that also works if the source (myObservable) is asynchronous, not only for this particular synchronous case.
You should use forkJoin for firing all the observables at the same time.
Here's an example without comments:
const { Observable } = Rx;
const obs$ = Observable
.of(3000, 3000, 1000)
.map(x => Observable.of(x).delay(x));
const allObsWithDelay$ = obs$.toArray();
const result$ = allObsWithDelay$
.switchMap(arr => Observable.forkJoin(arr));
result$
.do(console.log)
.subscribe();
And the same with explanation:
const { Observable } = Rx;
// source observable, emitting simple values...
const obs$ = Observable
.of(3000, 3000, 1000)
// ... which are wrapped into a different observable and delayed
.map(x => Observable.of(x).delay(x));
// use a reduce to build an array containing all the observables
const allObsWithDelay$ = obs$.toArray();
const result$ = allObsWithDelay$
// when we receive the array with all the observable
// (so we get one event, with an array of multiple observables)
.switchMap(arr =>
// launch every observable into this array at the same time
Observable.forkJoin(arr)
);
// display the result
result$
.do(console.log)
.subscribe();
With those values: 3000, 3000, 1000 the whole process is taking 3 seconds (the maximum of them as they're fired at the same time)
Working Plunkr: https://plnkr.co/edit/IRgEhdjCmZSTc6hSaVeF?p=preview
Edit 1: Thanks to #PierreCitror for pointing out toArray which is better than scan :)
I would do it like this:
myObservable
.mergeMap((val, i) => Observable.forkJoin(
Observable.of(i),
Observable.of(v).delay(v)
))
.scan((acc, ([i, result])) => {
acc[i] = result;
return acc;
}, {})
.filter(allResults => {
// Whatever goes here
Object.keys(allResults) // list indices of all finished responses
})
This will accumulate all responses in a single object where each response is assigned an index at which it arrived into mergeMap.
Then in filter you can write whatever logic you want that decides whether the current state should be propagated further (eg. you can wait until a certain number of responses arrived or whatever).

Creating an observable that emits values periodically (error: observer.timer is not a function)

I have an object that looks like this: [{ timestamp: object }]
The timestamp is created timestamp = Date.now();
Now, I am looking for a way to create an Observable that emits the objects in the real sequence, i.e. when the time between entries is 200ms then it needs to wait 200ms, if it's 2.5 seconds then it needs to wait 2500ms.
I get the difference by subtracting the two adjacent index values from each other.
My code is here:
startReplay() {
this.recordingRunning = true;
const replayValues = this.recordedListeningValues;
this.formObservable = Observable.create(function(observer) {
let lastTs;
Object.keys(replayValues).forEach(function(key) {
observer.timer(Math.floor((Number(key) - lastTs) / 1000)).next(key, lastTs);
lastTs = Number(key);
});
});
var source = this.formObservable.subscribe(x => console.log(x));
}
It throws the error: observer.timer is not a function
At the moment, I am only trying to log the differences between the timestamps in seconds. Now, I want to emit it in the differences between the two timestamps.
You can use delayWhen operator. But you need to know the timestamp of the start of the recording so that you can calculate the time of each event relative to the start of the recording
const startOfRecording = ??? // Date.now() when recording started
const recordedValues = ???
// each time someone subscribes to this observable
// the recorded events will play back in "real time"
this.formObservable = Observable
.from(Object.keys(replayValues))
.delayWhen(key => Observable.timer(key - startOfRecording))
.map(key => replayValues[key]);

Resources