I'm working on a turn-based game in Angular that communicates to a backend via a socket.io implementation. In my component, I am listening for several types of communication from the server, each communication gives information on how to update my view to reflect the current state of the data in the server.
Right now, updates are immediately applied to the component's data. However I'd prefer to render each update with some delay in-between, so that the user has time to see the effect of each update.
(See my image at top for essentially what I'm trying to do)
I believe that I would achieve this via the subscribeOn operator, but unsure of how to specify my 'interval' n.
const example = Rx.Observable
.create(observer => {
observer.next(0);
observer.next(1);
observer.next(2);
setTimeout(() => {
observer.next(3);
observer.next(4);
observer.complete();
}, 2500);
});
const source = example
.subscribeOn(Scheduler.timeout);
source.subscribe(console.log);
Use the concatMap operator as follows:
const nInterval = 500;
const example$ = Rx.Observable.from([0, 1, 2])
.concat(Rx.Observable.from([3,4]).delay(2500));
const source$ = example$
.concatMap(item =>
Rx.Observable.of(item)
.concat(
Rx.Observable.of('ignored')
.delay(nInterval)
.ignoreElements()
)
);
source$.subscribe(console.log);
<script src="https://cdnjs.cloudflare.com/ajax/libs/rxjs/5.5.2/Rx.min.js"></script>
Related
As I understand, in FRP (Functional Reactive Programming), we model the system as a component which receives some input signals and generates some output signals:
,------------.
--- input1$ --> | | -- output1$ -->
| System | -- output2$ -->
--- input2$ --> | | -- output3$ -->
`------------'
In this way, if we have multiple subsystems, we can plump them together as long as we can provide operators that can pipe inputs and outputs.
Now, I'm building an app, which processes video frames asynchronously. The actual processing logic is abstracted and can be provided as an argument. In non-FRP way of thinking, I can construct the app as
new App(async (frame) => {
return await processFrame(frame)
})
The App is responsible for establishing communication with underlying video pipeline and repeatedly get video frames and then pass that frame to the given callback, and once the callback resolves,App sends back the processed frame.
Now I want to model the App in a FRP way so I can flexibly design the frame processing.
const processedFrameSubject = new Subject()
const { frame$ } = createApp(processedFrameSubject)
frame$.pipe(
map(toRGB),
mergeMap(processRGBFrame),
map(toYUV)
).subscribe(processedFrameSubject)
The benefit is that it enables the consumer of createApp to define the processing pipeline declaratively.
However, in createApp, given a processedFrame, I need to reason about which frame it is related to. Since frame$ and processedFrameSubject is now separated, it's really hard for createApp to reason about which frame a processedFrame is related to, which was quite easy in non-FRP implementation because the frame and processedFrame were in same closure.
In functional reactive programming, you would avoid using side effects as much as possible, this means avoiding .subscribe(, tap(() => subject.next()), etc. With FRP, your state is declared on how it works and how it's wired up, but it doesn't execute until someone needs it and performs the side effect.
So I think that the following API would still be considered FRP:
function createApp(
processFrame: (frame: Frame) => Observable<ProcessedFrame>
): Observable<void>
const app$ = createApp(frame => of(frame).pipe(
map(toRGB),
mergeMap(processRGBFrame),
map(toYUV)
));
// `app$` is an Observable that can be consumed by composing it to other
// observables, or by "executing the side effect" by .subscribe() on it
// possible implementation of createApp for this API
function createApp(
processFrame: (frame: Frame) => Observable<ProcessedFrame>
) {
return new Observable<void>(() => {
const stopVideoHandler = registerVideoFrameHandler(
(frame: Frame) => firstValueFrom(processFrame(frame))
);
return () => {
// teardown
stopVideoHandler()
}
});
}
Something worth noting is that createApp is returning a new Observable. Inside new Observable( we can escape from FRP because it's the only way we can integrate with external parties, and all the side effects we have written won't be called until someone .subscribe()s to the observable.
This API is simple and would still be FRP, but it has one limitation: the processFrame callback can only process frames independently from others.
If you need an API that supports that, then you need to expose the frames$, but again, this is a project function for createApp:
function createApp(
projectFn: (frame$: Observable<Frame>) => Observable<ProcessedFrame>
): Observable<void>
const app$ = createApp(frame$ => frame$.pipe(
map(toRGB),
mergeMap(processRGBFrame),
map(toYUV)
));
// possible declaration of createApp
function createApp(
projectFn: (frame$: Observable<Frame>) => Observable<ProcessedFrame>
) {
return new Observable<void>(() => {
const frame$ = new Subject<Frame>;
const processedFrame$ = connectable(frame$.pipe(projectFn));
const processedSub = processedFrame$.connect();
const stopVideoHandler = registerVideoFrameHandler(
(frame: Frame) => {
// We need to create the promise _before_ we send in the next `frame$`, in case it's processed synchronously
const resultFrame = firstValueFrom(processedFrame$);
frame$.next(frame);
return resultFrame;
})
);
return () => {
// teardown
stopVideoHandler()
processedSub.unsubscribe();
}
});
}
I'm guessing here registerVideoFrameHandler will call the function one-by-one without overlap? If there's overlap then you'd need to track the frame number in some way, if the SDK doesn't give you any option, then try something like:
// Assuming `projectFn` will emit frames in order. If not, then the API
// should change to be able to match them
const processedFrame$ = connectable(frame$.pipe(
projectFn,
map((result, index) => ({ result, index }))
));
const processedSub = processedFrame$.connect();
let frameIdx = 0;
const stopVideoHandler = registerVideoFrameHandler(
(frame: Frame) => {
const thisIdx = frameIdx;
frameIdx++;
const resultFrame = firstValueFrom(processedFrame$.pipe(
filter(({ index }) => index === thisIdx),
map(({ result }) => result)
));
frame$.next(frame);
return resultFrame;
})
);
As part of learning rxjs ive been using create methods of, from, interval etc. to test throttle and deboucne etc ive been creating streams using fromevent.
now i have a real use case and i need to dynamically add values into an empty observable stream. i cant find any examples on how best to do this NOT using the creation methods above. Presently Im using a BehaviourSubject to dynamically add items to a stream using next(). Is this the best/preferred way of DYNAMICALLY adding new items to a stream?
e.g.
import { BehaviorSubject, timer } from 'rxjs';
import { tap, mapTo, concatMap, } from 'rxjs/operators';
const subject = new BehaviorSubject(1);
const example = subject.pipe(
concatMap(ev => timer(200).pipe(mapTo(ev))),
tap((ev) => console.log(ev))
)
example.subscribe();
// add a flurry of values dynamically
subject.next(2);
subject.next(3);
subject.next(4);
// some time later add some more
setTimeout(function(){
subject.next(5);
subject.next(6);
subject.next(7);
}, 5000);
https://stackblitz.com/edit/rxjs-behaviorsubject-simpleexample-gyrtw8?file=index.ts
Thanks
If you have a veeery custom logic of adding values that should be emitted in an Observable, you can create your own (instead of using fromEvent, of, from, ...):
const myObservable = new Observable(subscriber => {
subscriber.next(1);
subscriber.next(2);
subscriber.next(3);
setTimeout(() => {
subscriber.next(4);
subscriber.next(5);
setTimeout(() => {
subscriber.next(6);
}, 2000);
}, 1000);
});
However, the rxjs's creation functions should cover 99% of your needs.
The code above can be also written like:
concat(
of(1,2,3),
of(4,5).pipe(
delay(1000)
),
of(6).pipe(
delay(2000)
)
)
UPD: About Subjects
Subject is also an Observable so in your case using Subject is applicable but might not be the best option. The idea of a Subject is that there can be more than one subscriber (the one who uses the values from subject) but I'm not sure that it's your case (by the way - you can provide your real-life example to help us understand what you want to achieve)
Is there a way to add more operations to an observable that has already been subscribed? I tried the below code, which doesn't work, as the One more tap after subscribe part is not executed (code is here)
import { of, timer } from 'rxjs';
import { tap, map, take } from 'rxjs/operators';
const source = timer(1000, 1000);
//transparently log values from source with 'do'
const example = source.pipe(
take(3),
tap(val => console.log(`BEFORE MAP: ${val}`)),
map(val => val + 10),
tap(val => console.log(`AFTER MAP: ${val}`))
);
//'do' does not transform values
//output: 11...12...13...14...15
const subscribe = example.subscribe(val => console.log(val));
example.pipe(
tap(val => console.log(`One more tap after subscribe: ${val}`))
);
The use cas I have in mind is where for example I make an http call, and more than one service needs to be updated with the reponse of the call.
I will take this as what you ultimately want to achieve
The use cas I have in mind is where for example I make an http call, and more than one service needs to be updated with the reponse of the call.
const onExampleCalled=new Subject();
// listen to example called
onExampleCalled.subscribe(console.log)
example.pipe(tap(result=>onExampleCalled.next(result)).subscribe()
I am not quite sure what you try to achieve, but the pipe() function does not alter the source Observable. It just outputs a new Observable that results from the old one and the operators you aplied in the pipe(). Therefor your last line of code is like writing
5;
Meaning you set a value as a statement. Maybe you could reassign your Observable to itself after Transformation (although I am sure, that it would look quite ugly and will be hard to understand for others and should therefor be avoided).
example = example.pipe(
tap(val => console.log(`One more tap after subscribe: ${val}`))
);
Maybe you should go more into detail what you specific usecase is, so that we can find a cleaner solution.
I have spent few days but could not find a way to do "distinct throttle" in RxJS.
Assume each event completes in 4 dashes, a "distinct throttle" will perform as follows:
-①-②-①---------①-----|->
[distinct throttle]
-①-②-------------①-----|->
How can I use existing RxJS operators to build a "distinct throttle"?
You can use groupBy to separate the notifications by value and can then apply throttleTime and can then merge the grouped observables using mergeMap. Like this:
const { Subject } = rxjs;
const { groupBy, mergeMap, throttleTime } = rxjs.operators;
const source = new Subject();
const result = source.pipe(
groupBy(value => value),
mergeMap(grouped => grouped.pipe(
throttleTime(400)
))
);
result.subscribe(value => console.log(value));
setTimeout(() => source.next(1), 100);
setTimeout(() => source.next(2), 300);
setTimeout(() => source.next(1), 400);
setTimeout(() => source.next(1), 900);
.as-console-wrapper { max-height: 100% !important; top: 0; }
<script src="https://unpkg.com/rxjs#6/bundles/rxjs.umd.min.js"></script>
distinct and throttle have 2 different characteristics regarding item pick. distinct will pick the first item while throttle will pick the last.
Sometimes you want to keep throttle's behavior.
Let's say the stream is: chat-message-edit events carrying the updated text. A user may edit a specific message multiple times within the throttle period.
You want to be sure that you always keep the last version of each message (among a stream of edits of differrent messages ).
A possible solution I would follow for this is the one below
const source$ = from([
{id:1,content:"1a"},
{id:1,content:"1b"},
{id:1,content:"1c"},
{id:2,content:"2a"},
{id:2,content:"2b"},
{id:3,content:"3a"},
{id:3,content:"3b"},
{id:1,content:"1d"},
{id:1,content:"1e"},
{id:4,content:"4a"},
{id:4,content:"4b"},
{id:4,content:"4c"},
{id:4,content:"4e"},
{id:4,content:"4f"},
{id:3,content:"3c"},
{id:3,content:"3d"},
{id:3,content:"3e"}
]).pipe(concatMap((el)=> of(el).pipe(delay(500)) ));
const distinctThrottle = (throttleTime, keySelector)=>
pipe(bufferTime(throttleTime),
concatMap((arr)=>from(arr.reverse()).pipe(distinct(keySelector))
)) ;
let throttledStream = source$.pipe(distinctThrottle(1550, ({id})=>id));
throttledStream.subscribe(console.log);
Let's say I have a rather typical use of rx that does requests every time some change event comes in (I write this in the .NET style, but I'm really thinking of Javascript):
myChanges
.Throttle(200)
.Select(async data => {
await someLongRunningWriteRequest(data);
})
If the request takes longer than 200ms, there's a chance a new request begins before the old one is done - potentially even that the new request is completed first.
How to synchronize this?
Note that this has nothing to do with multithreading, and that's the only thing I could find information about when googling for "rx synchronization" or something similar.
You could use concatMap operator which will start working on the next item only after previous was completed.
Here is an example where events$ appear with the interval of 200ms and then processed successively with a different duration:
const { Observable } = Rx;
const fakeWriteRequest = data => {
console.log('started working on: ', data);
return Observable.of(data).delay(Math.random() * 2000);
}
const events$ = Observable.interval(200);
events$.take(10)
.concatMap(i => fakeWriteRequest(i))
.subscribe(e => console.log(e));
<script src="https://unpkg.com/rxjs/bundles/Rx.min.js"></script>