Using 2 observables with 1 output observable - rxjs

How can I have these 2 observables streamA$ and streamB$ run concurrently with click events?
The code I've got doesn't work properly: if I click A, then B while A is still in progress, B cancels A and vice versa. How can I have the 2 in parallel so if I click B while A is in progress, A continues and the result is assigned to images$ then if B finishes after, it replaces images$ so 2 results would appear in sequence. Then I could use waitUntil, filters etc to managae the 2 streams.
ngOnInit() {
this.streamA$ = this.streamService.getStream(1);
this.streamB$ = this.streamService.getSlowStream(2);
}
clickA() {
this.images$ = this.streamA$;
}
clickB() {
this.images$ = this.streamB$;
}
Template, subscribes to images$ with async
<div class="section">
{{images$ | async}}
</div>

You can setup a queue subject and concatMap you stream, it'll process in sequence and never miss any click.
private queue = new Subject()
public images$ = queue.pipe(concatMap(res => res))
clickA(){
this.queue.next(this.streamA$)
}
clickB(){
this.queue.next(this.streamB$)
}
https://stackblitz.com/edit/angular-z4zmd6

Related

RxJS waiting for response before sending next command over UDP

I am currently working on a project where I send UDP commands to a Tello drone.
The problem is that it uses UDP and when I send commands too fast before the previous one hasn't finished yet, the second command/action doesn't take place. I am using RxJS for this project and I want to create a mechanism to wait for the response ("ok" or "error") from the drone.
My Idea is to have 2 different observables. 1 observable that is the input stream from the responses from the drone and one observable of observables that I use as a commandQueue. This commandQueue has simple observables on it with 1 command I want to send. And I only want to send the next command when I received the "ok" message from the other observable. When I get the "ok" I would complete the simple command observable and it would automatically receive the next value on the commandQueue, being the next command.
My code works only when I send an array of commands, but I want to call the function multiple times, so sending them 1 by 1.
The following code is the function in question, testsubject is an observable to send the next command to the drone.
async send_command_with_return(msg) {
let parentobject = this;
let zeroTime = timestamp();
const now = () => numeral((timestamp() - zeroTime) / 10e3).format("0.0000");
const asyncTask = data =>
new Observable(obs => {
console.log(`${now()}: starting async task ${data}`);
parentobject.Client.pipe(take(1)).subscribe(
dataa => {
console.log("loool")
obs.next(data);
this.testSubject.next(data);
console.log(`${now()}: end of async task ${data}`);
obs.complete();
},
err => console.error("Observer got an error: " + err),
() => console.log("observer asynctask finished with " + data + "\n")
);
});
let p = this.commandQueue.pipe(concatMap(asyncTask)).toPromise(P); //commandQueue is a subject in the constructor
console.log("start filling queue with " + msg);
zeroTime = timestamp();
this.commandQueue.next(msg);
//["streamon", "streamoff", "height?", "temp?"].forEach(a => this.commandQueue.next(a));
await p;
// this.testSubject.next(msg);
}
streamon() {
this.send_command_with_return("streamon");
}
streamoff() {
this.send_command_with_return("streamoff");
}
get_speed() {
this.send_command_with_return("speed?");
}
get_battery() {
this.send_command_with_return("battery?");
}
}
let tello = new Tello();
tello.init();
tello.streamon();
tello.streamoff();
You can accomplish sending commands one at a time by using a simple subject to push commands through and those emissions through concatMap which will execute them one at a time.
Instead of trying to put all the logic in a single function, it will may be easier to make a simple class, maybe call it TelloService or something:
class TelloService {
private commandQueue$ = new Subject<Command>();
constructor(private telloClient: FakeTelloClient) {
this.commandQueue$
.pipe(
concatMap(command => this.telloClient.sendCommand(command))
)
.subscribe()
}
sendCommand(command: Command) {
this.commandQueue$.next(command);
}
}
When the service is instantiated, it subscribes to the commandQueue$ and for each command that is received, it will "do the work" of making your async call. concatMap is used to process commands one at a time.
Consumers would simply call service.sendCommand() to submit commands to the queue. Notice commands are submitted one at a time, it's not necessary to submit an array of commands.
Here is a working StackBlitz example.
To address your condition of waiting until you receive an ok or error response before continuing, you can use takeWhile(), this means it will not complete the observable until the condition is met.
To introduce a max wait time, you can use takeUntil() with timer() to end the stream if the timer emits:
this.commandQueue$
.pipe(
concatMap(command => this.telloClient.sendCommand(command).pipe(
takeWhile(status => !['ok', 'error'].includes(status), true),
takeUntil(timer(3000))
))
)
.subscribe()
Here's an updated StackBlitz.

Filtered send queue in rxjs

So I'm relatively inexperienced with rxjs so if this is something that would be a pain or really awkward to do, please tell me and I'll go a different route. So in this particular use case, I was to queue up updates to send to the server, but if there's an update "in flight" I want to only keep the latest item which will be sent when the current in flight request completes.
I am kind of at a loss of where to start honestly. It seems like this would be either a buffer type operator and/or a concat map.
Here's what I would expect to happen:
const updateQueue$ = new Subject<ISettings>()
function sendToServer (settings: ISettings): Observable {...}
...
// we should send this immediately because there's nothing in-flight
updateQueue$.next({ volume: 25 });
updateQueue$.next({ volume: 30 });
updateQueue$.next({ volume: 50 });
updateQueue$.next({ volume: 65 });
// lets assume that our our original update just completed
// I would now expect a new request to go out with `{ volume: 65 }` and the previous two to be ignored.
I think you can achieve what you want with this:
const allowNext$ = new Subject<boolean>()
const updateQueue$ = new Subject<ISettings>()
function sendToServer (settings: ISettings): Observable { ... }
updateQueue$
.pipe(
// Pass along flag to mark the first emitted value
map((value, index) => {
const isFirstValue = index === 0
return { value, isFirstValue }
}),
// Allow the first value through immediately
// Debounce the rest until subject emits
debounce(({ isFirstValue }) => isFirstValue ? of(true) : allowNext$),
// Send network request
switchMap(({ value }) => sendToServer(value)),
// Push to subject to allow next debounced value through
tap(() => allowNext$.next(true))
)
.subscribe(response => {
...
})
This is a pretty interesting question.
If you did not have the requirement of issuing the last in the queue, but simply ignoring all requests of update until the one on the fly completes, than you would simply have to use exhaustMap operator.
But the fact that you want to ignore all BUT the last request for update makes the potential solution a bit more complex.
If I understand the problem well, I would proceed as follows.
First of all I would define 2 Subjects, one that emits the values for the update operation (i.e. the one you have already defined) and one dedicated to emit only the last one in the queue if there is one.
The code would look like this
let lastUpdate: ISettings;
const _updateQueue$ = new Subject<ISettings>();
const updateQueue$ = _updateQueue$
.asObservable()
.pipe(tap(settings => (lastUpdate = settings)));
const _lastUpdate$ = new Subject<ISettings>();
const lastUpdate$ = _lastUpdate$.asObservable().pipe(
tap(() => (lastUpdate = null)),
delay(0)
);
Then I would merge the 2 Observables to obtain the stream you are looking for, like this
merge(updateQueue$, lastUpdate$)
.pipe(
exhaustMap(settings => sendToServer(settings))
)
.subscribe({
next: res => {
// do something with the response
if (lastUpdate) {
// emit only if there is a new "last one" in the queue
_lastUpdate$.next(lastUpdate);
}
},
});
You may notice that the variable lastUpdate is used to control that the last update in the queue is used only once.

Non-strict sequences with redux-observable and RxJS

My app has a modal with a spinner that's displayed whenever a long blocking action is taking place.
There's several of these long blocking actions, each with an action that marks its start and finish.
Given the "stream of actions", whenever one of the start action is dispatched, I want to dispatch the showWaitingIndication action until the corresponding end action is dispatched and then dispatch hideWaitingIndication. If another start action is dispatched and then it's corresponding end action is dispatched while the first blocking action is in progress, it shouldn't call showWaitingIndication again or hideWaitingIndication. Nor should hideWaitingIndication be dispatched while an action is still active.
Basically the idea is that as long as a blocking action is active, the waiting indication shouldn't hide.
e.g.
StartA -> dispatch(showWaitingIndication) -> other events -> endA -> dispatch(hideWaitingIndication)
StartA -> dispatch(showWaitingIndication) -> startB -> endB (shouldn't call hide) -> endA -> dispatch(hideWaitingIndication)
Also
StartA -> dispatch(showWaitingIndication) -> startB -> endA (shouldn't call hide!) -> endB -> dispatch(hideWaitingIndication)
I'm trying to wrap my head around how to implement this with streams (which I strongly believe are a good fit for this issue).
So far I've come up with something like this (which doesn't work)
let showHideActionPairs = getShowHideActionPairs(); // { "startA": "endA", "startB": "endB"}
let showActions = Object.keys(showHideActionPairs);
return action$ => action$.pipe(
filter(action => Object.keys(showHideActionPairs).includes(action.type)),
switchMap(val =>
{
let hideAction = showHideActionPairs[val.type];
return concat(
of(waitingIndicationShowAction),
empty().pipe(
ofType(hideAction),
mapTo(waitingIndicationHideAction)
))
}
)
);
What's the proper way of doing this?
This is a very interesting problem!
I think you could try this:
const showHideActionPairs = getShowHideActionPairs(); // { "startA": "endA", "startB": "endB"}
actions$.pipe(
windowWhen(() => actions$.pipe(filter(action => action.type === hideWaitingIndication))),
mergeMap(
window => window.pipe(
mergeMap(
action => someAsyncCall().pipe(
mapTo(showHideActionPairs[action]),
startWith(showHideActionPairs[action])
)
),
scan((acc, crtEndAction) => {
// first time receiving this end action -> the beginning of the async call
if (!(crtEndAction in acc)) {
acc[crtEndAction] = true;
return acc;
}
// if the `crtEndAction` exists, it means that the async call has finished
const {[crtEndAction]: _, ...rest} = acc;
return rest;
}, Object.create(null)),
filter(obj => Object.keys(obj).length === 0),
mapTo(hideWaitingIndication),
// a new window marks the beginning of the modal
startWith(showWaitingIndication),
)
)
)
My first thought was that I need to a find a way to represent a chain of events, such that the chain starts at showWaitingIndication and ends with hideWaitingIndication. The end of the chain is actually indicated by the last completed async call(end{N}). So I considered it would be a good use case for windowWhen.
But what is a window ? A window is nothing more than a Subject:
/* ... */
const window = this.window = new Subject<T>();
this.destination.next(window);
/* ... */
The way windowWhen(() => closeNotifier) works is that it will send a Subject(a window) as a next value(that's why we have mergeMap(window => ...)) and it will push values(e.g actions) through it. We're accessing these values inside window.pipe(...). When closeNotifier emits, the current window will complete and a new window will be created and passed along, so that subsequent actions will be sent through it. It's worth noting that a window is created by default when the stream is subscribed:
constructor(protected destination: Subscriber<Observable<T>>,
private closingSelector: () => Observable<any>) {
super(destination);
this.openWindow(); // !
}
Let's say that a we're receiving the first action in the current window.
mergeMap(
action => someAsyncCall().pipe(
mapTo(showHideActionPairs[action]),
startWith(showHideActionPairs[action])
)
),
As soon as the action is intercepted, we'll send its expected end value, so that it can be stored in the scan's accumulator. When that async call of that action would be finished, it will send again that end value, so that it can be removed from the accumulator.
This way, we can determine the lifespan of a window, which will be closed when there are no more end values in the accumulator.
When this happens
filter(obj => Object.keys(obj).length === 0),
mapTo(hideWaitingIndication),
we make sure that we notify that all the actions have finished their task.
I've accepted Andrei's answer, as he was to one to point me out in the right direction, and his solution involving a windowWhen and accumulator was the right mind frame to tackle this issue. I'm also posting my own solution based on his for completeness' sake, as I feel the logic here is more explicit (and personally was easier for me to wrap my head around as I was searching for the solution):
let showHideActionPairs = getShowHideActionPairs();
const relevantActionsTypesArray = Object.keys(showHideActionPairs).concat(Object.values(showHideActionPairs));
actions$ => actions$.pipe(
// close the "window" when a hide action is received
windowWhen(() => actions$.pipe(ofType(waitingIndicationHideActionName),)),
mergeMap(
window => window.pipe(
// filter to only look at start/end actions
ofType.apply(null, relevantActionsTypesArray),
scan((accumulator, action) => {
let waitingForEndAction = "startAction" in accumulator;
// first time we see a start action
if (!waitingForEndAction && action.type in showHideActionPairs) {
accumulator.startAction = action.type;
accumulator.actionable = true;
// found the right end action
} else if (waitingForEndAction && action.type === showHideActionPairs[accumulator.startAction]) {
accumulator.endAction = action.type;
accumulator.actionable = true;
// any other case is not actionable (will not translate to to an action)
} else {
accumulator.actionable = false;
}
return accumulator;
}, {}),
// accumulator spits out stuff for every action but we only care about the actionables
filter(obj => obj.actionable),
map(obj => {
if (obj.endAction){
return waitingIndicationHideAction
} else if (obj.startAction) {
return waitingIndicationShowAction
}
}),
)
)
)
};

RxJS Unsubscribe Only From Inner Observable

Let's say I have an interval that each second sends an heartbeat. At each beat i'd like to inspect something on my web page and react accordingly. I'd also like the option to unsubscribe from the inner Observables actions, but keep getting the heartbeat so when i subscribe back, everything will flow as before.
Creating a Subscription from Interval and piping it leaves no option to unsubscribe from the inner action, but only the whole subscription as whole.
Is there a way to return the inner Observable so i can unsubscribe from it while still retaining the heartbeat created from the Interval?
Edit: I've tried to create a class to describe what I'm talking about:
class Monitor {
sub: Subscription | null = null;
start() {
this.sub = this.monitor().subscribe();
}
monitor() {
const dom$ = someSelectorObserver(this.win.document, '#someSelector').pipe(
mergeMap(newElementOrBail => {
if (newElementOrBail) {
return handle(newElementOrBail);
} else {
return bail();
}
}),
tap({
error: error => this.log.error(error),
}),
);
return dom$;
}
handle(ele: HTMLElement) {
// do stuff
}
bail() {
this.sub.unsubscribe();
}
}
So basically my monitor starts with creating the subscription, as long as there's a new element to handle everything is fine, but when a bail signal appears I'd like to unsubscribe while still monitoring the DOM changes for a return of the previous elements.
So the outer subscription is basically the DOM observer and the inner is the mergeMap handle function. Does it make more sense?
You could just put some conditional on your inner observable:
private takeSignal = true
interval(3000).pipe(switchMap(() => takeSignal ? inner$ : NEVER))
Then just flip takeSignal as needed.
But it seems easier to just unsubscribe from the whole thing and resubscribe when needed. Why keep the interval going when you’re not using it?
You can split your logic in two (or more) streams.
Store heartbeat$ in a separate variable and subscribe to multiple times for different reasons.
In this way, you'd be able to split your logic into different streams and control subscriptions individually.
const heartbeat$ = interval(3000);
const inspectWeb = heartbeat$.pipe(
// do stuff
).subscribe()
inspectWeb.unsubscribe()
heartbeat$.pipe(
// do other stuff
).subscribe()

Observable - Getting the value of the latest emission

I have a form and I allow the user to click as many times as he wants on a refresh button. Of course, I use debounceTime operator but I don't know how to:
either cancel the previous http requests
or indicate to my service to return the value of the latest emission.
For example:
t1: click => received data in 2000ms
t2: click => received data in 200ms
Therefore, I will get the data from t1 moment whereas the latest one is at t2.
I've tried with pipe(last()), switchMap but I don't return data.
My component:
this.filtersForm.valueChanges.pipe(debounceTime(500)).subscribe(
form => {
this.service.setFilters(form); // Set private field in service (1)
this.onSubmit();
}
);
onSubmit() {
if (this.filtersForm.valid) {
this.service.notifFiltersHasChanged();
}
}
Service:
ctor(...) {
this.filters$.subscribe(f => this.getData());
}
notifFiltersHasChanged() {
this.filters$.next(this._filters); // (1) _filters is set by setFilters method
}
getData(): void {
// ...
this.backEndService.getAll(this._filters).subscribe(data => this._data = data);
}
BackEndService:
getAll(filters: any): Observable<Data> {
return this.httpClient.get<Data>(url).pipe(last());
}
The main trick is to use a single subscription (or even zero, if you'll use | async pipe in your template). So you source from an Observable and chain through your services.
Heres an updated example of yours:
Component
onDestroy$ = new Subject<void>();
constructor(){
this.filtersForm.valueChanges.pipe(
// accept only valid values
filter(() => this.filtersForm.valid),
// debounce them
debounceTime(500),
// when a value comes in -- we switch to service request
// subsequent values would cancel this request
switchMap(formValues => this.service.getData(formValues)),
// this is needed to unsubscribe from the service
// when component is destroyed
takeUntil(this.onDestroy$)
)
.subscribe(data=>{
// do what you need with the data
})
}
ngOnDestroy() {
this.onDestroy$.next(void 0);
}
Service
// service becomes stateless
// its only responsible for parsing and passing data
getData(filters): Observable<Data> {
return this.backEndService.getAll(filters);
}
BackEndService
getAll(filters: any): Observable<Data> {
return this.httpClient.get<Data>(url).pipe(last());
}
Another way would be to have a Subject, that you would push to. Otherwise it would be the same chaining on top of that Subject.
Hope this helps

Resources