Replay Subject from Observable (emit all previous events) - rxjs

I have an observable and I'm trying to make a replay subject from. It should emit the current and all previous events that the observable emitted.
Here is what I thought would work, as per this answer:
// create an observable from dummy array and a blank replay subject
const observable$ = from([1, 2, 3])
const replay$ = new ReplaySubject()
// create a replay subject from the observable (not working as expected)
observable$.subscribe(replay$)
// log out the events
observable$.subscribe(e => console.log('observable', e) )
replay$.subscribe(e => console.log('replay', e))
Logs
observable 1
observable 2
observable 3
replay 1
replay 2
replay 3
The behavior I'm looking for is such that the replay subject emits the previous events as well, like so:
replay [1]
replay [1, 2]
replay [1, 2, 3]
How can I archieve this?

ReplaySubject replays the whole sequence only on subscription. This means only when you call replay$.subscribe(). After that it only passes through all emissions.
From what output you want to get it looks like you want to chain it with scan() because ReplaySubject emits items one by one and not as accumulated arrays as you expect.
observable$.pipe(
scan((acc, val) => [...acc, val], []),
).subscribe(replay$);
Live demo: https://stackblitz.com/edit/rxjs-yfgkvf?file=index.ts

Related

How to organize async response in RxJS (or FRP more generically)

Let's say we have a source$ observable, which is usually user interaction. And we want to perform an async operation on each firing of source$ and "map" the async result to an output observable, say result$.
mergeMap
The naivest implementation is
result$ = source$.pipe(
mergeMap((s) => someAsyncOperation(s))
)
However, it is possible for a previous response to override most recent response because someAsyncOperation may spend different amount of time for each round.
source: -----1-----2------->
result1: -----------|
result2: --|
result: -------------2--1-->
The last value on result$ observable is 1, which is incorrect as we have already triggered the operation for 2 and the response 2 has already arrived.
switchMap
We can replace mergeMap with switchMap and the graph would be:
source: -----1-----2------->
result1: -----------|
result2: --|
result: -------------2----->
For typical use cases like search suggestion, switchMap is desirable since the response-1 is most likely to be valueless once action-2 is fired.
Problem
But for some other cases, responses for previous actions may still be valid. For example, for a periodic polling scenario, responses are valuable as long as their chronical order is perserved.
source: -----1-----2----3------->
result1: --------|
result2: -----------|
result3: ----|
mergeMap: -----------1------3-2->
switchMap:------------------3--->
expected: -----------1------3--->
It's obvious that response-1 and response-3 are both desirable as they arrive in chronical order (while response-2 is invalid because it arrives after response-3).
The problem with mergeMap is that it cannot omit the invalid response-2.
While switchMap is also suboptimal because it omits a desirable value response-1 as the second observable has already started when response-1 arrives. The problem of switchMap worsens when the average RTT is larger than the polling interval.
source: -----1----2----3----4----5----->
result1: --------|
result2: --------|
result3: --|
result4: -------|
result5: ----|
switchMap:---------------3------------->
expected: -----------1---3---------4-5->
You can see switchMap generates far less outputs than the ideal one
Question
How should I get the expected output observable in this case?
You can attach an "emission index" to each response and use that to filter out older emissions.
const result$ = source$.pipe(
mergeMap((s, index) => someAsyncOperation(s).pipe(
map(response => ({ response, index }))
)),
scan((prev, cur) => ({...cur, maxIndex: Math.max(prev.maxIndex, cur.index)}), INITIAL),
filter(({index, maxIndex}) => index === maxIndex),
map(({response}) => response),
);
Here we can use scan to keep track of the highest emitted index thus far, then use filter to prevent emissions from older requests.
Here's a working StackBlitz demo.

Filter a Flux with a HashSet cellected from another Flux

I have two flux coming from ReactiveMongoDB API:
the first will gets the data of date1
the second one gets the data of date2
by the end i want to exclude elements that still appearing from the date1 in date2, In order to get those that are disappeared and save them in another collection.
To deal with That I used a HashSet to collect the element of the second date (date2), so I can filter the flux of the first date (date2) by excluding the element in the hashset.
Flux<Report> reportOfCurrentDate = reportRepository.get(LocalDate.now());
Flux<Report> reportOfPrevDate = reportRepository.get(LocalDate.now().minusDays(1));
reportOfCurrentDate.log("insert In Set :").collect(Collectors.toSet());
// filtering
reportOfPrevDate
.filterWhen(
report -> resourceIds
.map(installedResId -> !installedResId.contains(report.getInstalledResInstalledResourceId()))
)
.transform(rereports -> resolvedRepository.insertAll(reports.collectList())
.blockLast();
Note that the flux is getting the data form a very large collection
The problem is i get just one flux running (the one that insert in a set) it doesn't seem reactive the behavior expected is that the two fluxs will run in parallel, filtering data, and insert in the end in a new collection.
any one can help me with that, or suggest another way of doing it ?

How to view all records with rating 1, 3 and 5 Laravel query

I am trying to filter by rating. It can be for a record from 1 to 5. From the frontend comes a string, for example "1,3,5", which means - show all entries with a rating of 1, 3 and 5 at once.
$reviews = $this->data['sort_by'] = Review::query()
->where('rating', $data['rating'])
->get()
This is how I can get with only one value, but I need several at once. Also, the difficulty is that there can be not only "1, 3, 5", but also any other combinations, for example, "1, 4, 3" or "3,5,2"
If someone means how to compose a request before the get () method, it would be ideal so that all the work is at the database level and the server does not have to filter the collection, but in any case, I will be very happy with any solution where there will be only one request to the database data. Of course, I can make a separate request for each rating and glue them later, but that would be bad practice.
explode the posted rating so it becomes an array and use whereIn() instead of where():
$reviews = $this->data['sort_by'] = Review::query()
->whereIn('rating', explode(',', $data['rating']))
->get()

Replay cached items in reverse order on subscribe

I have a ConnectableObservable which upon subscribe, will replay the last x items in their original order (oldest to newest) and any subsequent events after.
I am using this Observable as the backing store for an event blotter, however upon subscribe I would actually like the replayed items to be pushed/onNext'ed in the reverse order (newest to oldest) so I can display the most relevant items first.
Is this possible with standard RX operators or will I have to create a custom one?
You can't do it with replay() as you'd need to get only the cached items on a non-terminated source. However, ReplaySubject lets you peek into it and get an array of items that you can reverse, then concatenate with the rest from the same subject but skipping the snapshot items just retrieved:
ReplaySubject<ItemType> subject = ReplaySubject.create();
source.subscribe(subject);
Observable<ItemType> result = Observable.defer(() -> {
ItemType[] current = subject.getValues(new ItemType[0]);
return Observable.range(0, current.length)
.map(index -> current[current.length - 1 - index])
.concatWith(subject.skip(current.length));
});

how best to refactor two classes with lots of conditional logic?

I have an Event class which holds start and end times for an event. Each Event object can have a number of associated ChildEvent objects representing each recurrence of the parent Event. Each of those classes needs to take a different action based on how they are being edited e.g.
Deleting just one Event:
Event: make the first child the parent of all other children before deleting
ChildEvent: just delete as normal
Deleting an Event and all subsequent events:
Event: delete all child events then delete itself
ChildEvent: delete all future siblings then delete itself
Editing just one Event:
Event: make the first child the parent of all other children then update
ChildEvent: update itself as usual
Editing an Event and all subsequent events:
Event: update all child events then update itself
ChildEvent: update all future siblings then update itself
At present I'm achieving this through checking for conditions and taking appropriate action but it's starting to get messy (there are other conditions with associated behaviours too). I'm curious to know how more experienced programmers would handle this (I'm using ruby). Any ideas?
Sounds like a case for the specification pattern to encapsulate your logic
Why don't you just link the events, in the form of a doubly linked list?
An Event would have a previous and a next slot (nil if first or last of a chain).
Deleting just one Event (delete):
set the previous slot of next Event to the previous slot of this Event
set the next slot of the previous Event to the next slot of this Event
delete this Event
Deleting an Event and all subsequent events (delete-all):
Delete this Event
set next slot of previous Event to nil
recurse on next Event
until next is nil
Editing just one Event (edit):
edit this Event
set previous slot of next Event to previous slot of this Event
set next slot of previous Event to next slot of this Event
Editing an Event and all subsequent events (edit-all):
if initial call, set next slot of previous Event to nil
edit this Event
recurse on next Event
until next is nil
I suggest trying a different object model. Instead of Events and ChildEvents you could look at it as EventTypes and Events.
EventType # Has many Events
---------
name
# ...
Event # Belongs to EventType
-----
event_id
start_time
end_time
# ...
Then your editing operations would be greatly simplified.
Find previous Event...
Event.find(:first, :conditions => [
'event_type_id = ? AND start_time > ?',
event.type.id,
event.start_time],
:order => 'ASC'
)
Delete an Event and all subsequent events of that type...
events_to_delete = Event.find(:all,
:conditions => [
'event_type_id = ? AND start_time >= ?',
event.event_type.id,
event.start_time
])
Event.destroy( all_events_to_delete.map { |event| event.id } )

Resources