RxJS waiting for response before sending next command over UDP - rxjs

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.

Related

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.

How can I wait for multiple subscriptions?

I'm trying to make an API call for each element of an array, and emit an specific event if every API response is true.
I was doing the following:
let emit = true
array.forEach(element => {
this.service.getElement(element).subscribe(loaded => {
if(!loaded){
emit = false;
}
});
});
this.loaded.emit(emit);
But the last line always emits true
How can I wait for every request to be resolved before making the output event emission?
So the last line will always emit true because the service code is executed asynchronously. This means that it executes everything except the callback function in the subscribe method. This callback function executed once the stream emits a new element. I guess, you are making Http requests to an endpoint here. If so, the callback function is executed for each Http response and the order is not guaranteed.
What you can do is:
forkJoin(
...array.map(e => this.service.getElement(e))
).subscribe(responseArray =>
console.log(responseArray);
console.log('Complete');
);
The callback only executes if all Http response are available. And responseArray has the exact same order as the elements in the array.
Note: Keep in mind that if you do
let emit = false;
forkJoin(...).subscribe(() => emit = true);
console.log(emit);
It would print you false, because the callback executes async. If that seems like strange behaviour for you, I'd highly recommend to read about the JavaScript Eventloop.
Cause call http API is async, so this.loaded.emit(emit) executed first;
fix:
let emit = true
array.forEach(element => {
this.service.getElement(element).subscribe(loaded => {
if(!loaded){
emit = false;
this.loaded.emit(emit);
}
});
});
If you want execute this.loaded.emit(emit) when all of API response is true, try use forkJoin.

RxJS: Auto (dis)connect on (un)subscribe with Websockets and Stomp

I'm building a litte RxJS Wrapper for Stomp over Websockets, which already works.
But now I had the idea of a really cool feature, that may (hopefully - correct me if I'm wrong) be easily done using RxJS.
Current behavior:
myStompWrapper.configure("/stomp_endpoint");
myStompWrapper.connect(); // onSuccess: set state to CONNECTED
// state (Observable) can be DISCONNECTED or CONNECTED
var subscription = myStompWrapper.getState()
.filter(state => state == "CONNECTED")
.flatMap(myStompWrapper.subscribeDestination("/foo"))
.subscribe(msg => console.log(msg));
// ... and some time later:
subscription.unsubscribe(); // calls 'unsubscribe' for this stomp destination
myStompWrapper.disconnect(); // disconnects the stomp websocket connection
As you can see, I must wait for state == "CONNECTED" in order to subscribe to subscribeDestination(..). Else I'd get an Error from the Stomp Library.
The new behavior:
The next implementation should make things easier for the user. Here's what I imagine:
myStompWrapper.configure("/stomp_endpoint");
var subscription = myStompWrapper.subscribeDestination("/foo")
.subscribe(msg => console.log(msg));
// ... and some time later:
subscription.unsubscribe();
How it should work internally:
configure can only be called while DISCONNECTED
when subscribeDestination is called, there are 2 possibilities:
if CONNECTED: just subscribe to the destination
if DISCONNECTED: first call connect(), then subscribe to the destination
when unsubscribe is called, there are 2 possibilities:
if this was the last subscription: call disconnect()
if this wasn't the last subscription: do nothing
I'm not yet sure how to get there, but that's why I ask this question here ;-)
Thanks in advance!
EDIT: more code, examples and explanations
When configure() is called while not disconnected it should throw an Error. But that's not a big deal.
stompClient.connect(..) is non-blocking. It has an onSuccess callback:
public connect() {
stompClient.connect({}, this.onSuccess, this.errorHandler);
}
public onSuccess = () => {
this.state.next(State.CONNECTED);
}
observeDestination(..) subscribes to a Stomp Message Channel (= destination) and returns an Rx.Observable which then can be used to unsubscribe from this Stomp Message Channel:
public observeDestination(destination: string) {
return this.state
.filter(state => state == State.CONNECTED)
.flatMap(_ => Rx.Observable.create(observer => {
let stompSubscription = this.client.subscribe(
destination,
message => observer.next(message),
{}
);
return () => {
stompSubscription.unsubscribe();
}
}));
}
It can be used like this:
myStompWrapper.configure("/stomp_endpoint");
myStompWrapper.connect();
myStompWrapper.observeDestination("/foo")
.subscribe(..);
myStompWrapper.observeDestination("/bar")
.subscribe(..);
Now I'd like to get rid of myStompWrapper.connect(). The code should automatically call this.connect() when the first one subscribes by calling observeDestination(..).subscribe(..) and it should call this.disconnect() when the last one called unsubscribe().
Example:
myStompWrapper.configure("/stomp_endpoint");
let subscription1 = myStompWrapper.observeDestination("/foo")
.subscribe(..); // execute connect(), because this
// is the first subscription
let subscription2 = myStompWrapper.observeDestination("/bar")
.subscribe(..);
subscription2.unsubscribe();
subscription1.unsubscribe(); // execute disconnect(), because this
// was the last subscription
RxJS: Auto (dis)connect on (un)subscribe with Websockets and Stomp
I agree the code you are suggesting to tuck away into myStompWrapper will be happier in its new home.
I would still suggest to use a name like observeDestination rather than subscribeDestination("/foo") as you are not actually subscribing from that method but rather just completing your observable chain.
configure() can only be called while DISCONNECTED
You do not specify here what should happen if it is called while not DISCONNECTED. As you do not seem to be returning any value here that you would use, I will assume that you intend to throw an exception if it has an inconvenient status. To keep track of such statuses, I would use a BehaviourSubject that starts with the initial value of DISCONNECTED. You likely will want to keep state within observeDestination to decide whether to throw an exception though
if CONNECTED: just subscribe to the destination
if DISCONNECTED: first call connect(), then subscribe to the destination
As I mentioned before, I think you will be happier if the subscription does not happen within subscribeDestination("/foo") but rather that you just build your observable chain. As you simply want to call connect() in some cases, I would simply use a .do() call within your observable chain that contains a condition on the state.
To make use of the rx-y logic, you likely want to call disconnect() as part of your observable unsubscribe and simply return a shared refcounted observable to start with. This way, each new subscriber does not recreate a new subscription, instead .refCount() will make a single subscription to the observable chain and unsubscribe() once there is no more subscribers downstream.
Assuming the messages are coming in as this.observedData$ in myStompWrapper My suggested code as part of myStompWrapper would look something like this:
observeDestination() {
return Rx.Observable.create(function (observer) {
var subscription = this.getState()
.filter(state => state == "CONNECTED")
.do(state => state ? this.connect() : Observable.of(true))
.switchMap(this.observedData$)
.refCount();
.subscribe(value => {
try {
subscriber.next(someCallback(value));
} catch(err) {
subscriber.error(err);
}
},
err => subscriber.error(err),
() => subscriber.complete());
return { unsubscribe() { this.disconnect(); subscription.unsubscribe(); } };
}
Because I am missing some of your code, I am allowing myself to not test my code. But hopefully it illustrates and presents the concepts I mentioned in my answer.

RxJS 5 Timed Cache

I am trying to get time expiry cache to work for an observable that abstracts a "request-response", using postMessage and message events on the window.
The remote window expects a message getItemList and replies to it with a message of type {type: 'itemList', data: []}.
I would like to model the itemList$ observable in such a way that it caches the last result for 3 seconds, so that no new requests are made during that time, however, I cannot think of a way to achieve that in an elegant (read, one observable – no subjects) and succint manner.
Here is the example in code:
const remote = someIframe.contentWindow;
const getPayload = message => message.data;
const ofType = type => message => message.type === type;
// all messages coming in from the remote iframe
const messages$ = Observable.fromEvent(window, 'message')
.map(getPayload)
.map(JSON.parse);
// the observable of (cached) items
const itemList$ = Observable.defer(() => {
console.log('sending request');
// sending a request here, should happen once every 3 seconds at most
remote.postMessage('getItemList');
// listening to remote messages with the type `itemList`
return messages$
.filter(ofType('itemList'))
.map(getPayload);
})
.cache(1, 3000);
/**
* Always returns a promise of the list of items
* #returns {Promise<T>}
*/
function getItemList() {
return itemList$
.first()
.toPromise();
}
// poll every second
setInterval(() => {
getItemList()
.then(response => console.log('got response', response));
}, 1000);
I am aware of the (very similar) question, but I am wondering if anyone can come up with a solution without explicit subjects.
Thank you in advance!
I believe you are looking for the rxjs operator throttle:
Documentation on rxjs github repo
Returns an Observable that emits only the first item emitted by the
source Observable during sequential time windows of a specified
duration.
Basically, if you would like to wait until the inputs have quieted for a certain period of time before taking action, you want to debounce.
If you do not want to wait at all, but do not wish to make more than 1 query within a specific amount of time, you will want to throttle. From your use case, I think you want to throttle

How to merge the value of a Subject "next" into the main stream with Rxjs

The method retryWhen returns a Subject, how can I merge it's next value with the main stream ?
dataStream.
retryWhen(errors => {
return errors.
delay(1000).
take(3).
concat(Rx.Observable.throw(errors));
}).
subscribe(
x => console.log('onNext:', x),
e => {
e.subscribe(function (value) {
console.log('err', value);
});
},
_ => console.log('onCompleted'));
Right now it works but I have to subscribe to the e in the error handler to get the value of the error thrown by dataStream. Is it possible to simplify this ?
updates:
I would like to code a "data-access routine" with resilience. Currently It will retry 3 times with a delay of 1sec and in the case of an error, It will throw the "error". The problem is it doesn't throw an error it throws a "Subject" and I have to subscribe to it to get the error.
let getPromiseStream = function (endpoint) {
return Rx.Observable.
just(endpoint).
flatMap(requestUrl => {
return _this.$http.get(requestUrl);
});
};
let itemsStream = getPromiseStream('/items');
let locksStream = getPromiseStream('/error');
let usersStream = getPromiseStream('/users');
let favsStream = getPromiseStream('/favs');
let dataStream = Rx.Observable.
zip(
itemsStream,
locksStream,
usersStream,
favsStream,
(items, locks, users, favs) => {
return {items: items.data, locks: locks.data, users: users.data, favs: favs.data};
});
retryWhen can be a little difficult to use (I had to look it up again to remember its behavior). Essentially retryWhen will take all errors that you receive from the source Observable and convert them into onNext calls. The function you pass in takes in that Observable and then lets you play with the events passing through. If you want to continue retrying it should simply pass the event through. If it should error then you need to convert the onNext into a onError or if you just want it to stop trying then you should convert it into an onCompleted.
So onto your code you want to delay a certain number of times and then error the whole thing out if it really is more than just a transient network error.
dataStream.
retryWhen(errors => {
return errors
.flatMap((err, count) => {
return count < 3 ?
//This will just get flattened out and passed through
Rx.Observable.just(err) :
//If you have received 3 errors it is time to throw
//This error will get passed all the way to the final
//onError method if it isn't caught along the way
Rx.Observable.throw(err);
})
.delay(1000);
})
.subscribe(
x => console.log('onNext:', x),
e => console.log('err', value),
_ => console.log('onCompleted'));
Additional note: I would suggest that you put the retryWhen on each Observable that feeds into your zip rather than on the whole thing. As in the latter case a failure from one will retry all the source Observables not just the one that failed.

Resources