How to model asynchronous callback in functional reactive programming? - rxjs

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;
})
);

Related

How do I append to an observable inside the observable itself

My situation is as follows: I am performing sequential HTTP requests, where one HTTP request depends on the response of the previous. I would like to combine the response data of all these HTTP requests into one observable. I have implemented this before using an async generator. The code for this was relatively simple:
async function* AsyncGeneratorVersion() {
let moreItems = true; // whether there is a next page
let lastAssetId: string | undefined = undefined; // used for pagination
while (moreItems) {
// fetch current batch (this performs the HTTP request)
const batch = await this.getBatch(/* arguments */, lastAssetId);
moreItems = batch.more_items;
lastAssetId = batch.last_assetid;
yield* batch.getSteamItemsWithDescription();
}
}
I am trying to move away from async generators, and towards RxJs Observables. My best (and working) attempt is as follows:
const observerVersion = new Observable<SteamItem>((subscriber) => {
(async () => {
let moreItems = true;
let lastAssetId: string | undefined = undefined;
while (moreItems) {
// fetch current batch (this performs the HTTP request)
const batch = await this.getBatch(/* arguments */, lastAssetId);
moreItems = batch.more_items;
lastAssetId = batch.last_assetid;
const items = batch.getSteamItemsWithDescription();
for (const item of items) subscriber.next(item);
}
subscriber.complete();
})();
});
Now, I believe that there must be some way of improving this Observer variant - this code does not seem very reactive to me. I have tried several things using pipe, however unfortunately these were all unsuccessful.
I found concatMap to come close to a solution. This allowed me to concat the next HTTP request as an observable (done with the this.getBatch method), however I could not find a good way to also not abandon the response of the current HTTP request.
How can this be achieved? In short I believe this problem could be described as appending data to an observable inside the observable itself. (But perhaps this is not a good way of handling this situation)
TLDR;
Here's a working StackBlitz demo.
Explanation
Here would be my approach:
// Faking an actual request
const makeReq = (prevArg, response) =>
new Promise((r) => {
console.log(`Running promise with the prev arg as: ${prevArg}!`);
setTimeout(r, 1000, { prevArg, response });
});
// Preparing the sequential requests.
const args = [1, 2, 3, 4, 5];
from(args)
.pipe(
// Running the reuqests sequantially.
mergeScan(
(acc, crtVal) => {
// `acc?.response` will refer to the previous response
// and we're using it for the next request.
return makeReq(acc?.response, crtVal);
},
// The seed(works the same as `reduce`).
null,
// Making sure that only one request is run at a time.
1
),
// Combining all the responses into one object
// and emitting it after all the requests are done.
reduce((acc, val, idx) => ({ ...acc, [`request${idx + 1}`]: val }), {})
)
.subscribe(console.warn);
Firstly, from(array) will emit each item from the array, synchronously and one by one.
Then, there is mergeScan. It is exactly the result of combining scan and merge. With scan, we can accumulate values(in this case we're using it to have access to the response of the previous request) and what merge does is to allow us to use observables.
To make things a bit easier to understand, think of the Array.prototype.reduce function. It looks something like this:
[].reduce((acc, value) => { return { ...acc }}, /* Seed value */{});
What merge does in mergeScan is to allow us to use the accumulator something like (acc, value) => new Observable(...) instead of return { ...acc }. The latter indicates a synchronous behavior, whereas with the former we can have asynchronous behavior.
Let's go a bit step by step:
when 1 is emitted, makeReq(undefined, 1) will be invoked
after the first makeReq(from above) resolves, makeReq(1, 2) will be invoked
after makeReq(1, 2) resolves, makeReq(2, 3) will be invoked and so on...
Somebody I consulted regarding this matter came up with this solution, I think it's quite elegant:
defer(() => this.getBatch(options)).pipe(
expand(({ more_items, last_assetid }) =>
more_items
? this.getBatch({ ...options, startAssetId: last_assetid })
: EMPTY,
),
concatMap((batch) => batch.getSteamItemsWithDescription()),
);
From my understanding the use of expand here is very similar to the use of mergeScan in #Andrei's answer

Subject-like RxJS Observable that transparently pipes through flatMap

When using Dependency injection in Angular I often need to subscribe to an observable that I haven't yet created!
I often end up using something like this:
// create behavior subject OF Observable<number>
const subject = new BehaviorSubject<Observable<number>>(EMPTY);
// subscribe to it, using flatMap such as to 'unwrap' the observable stream
const unwrappedSubject = subject.pipe(flatMap((x: number) => x));
unwrappedSubject.subscribe(s => console.log(s));
// now actually create the observable stream
const tim = timer(1000, 1000);
// set it into the subject
subject.next(tim);
This uses flatMap to 'unwrap' the observable contained in the subject.
This works fine, but frankly it always feels 'icky'.
What I really want is something like this, where the consumer of the subject treats the instance of the Subject as Observable<number> without having to pipe it every usage.
const subject = new UnwrappingBehaviorSubject<number>(EMPTY);
subject.subscribe((x: number) => console.log(x));
// this could use 'next', but that doesn't feel quite right
subject.setSource(timer(1000, 1000));
I'm aware that I could subscribe to the timer and hook it up directly to the subject, but I also want to avoid an explicit subscribe call because that complicates the responsibility of unsubscribing.
timer(1000, 1000).subscribe(subject);
Is there a nice way to achieve this?
The Subject.ts and BehaviorSubject.ts source files get more complicated than I expected. I'm scared I'll end up with horrible memory leaks if I try to fork it.
I think this would be another way to solve it:
foo.component.ts
export class FooComponent {
private futureObservable$ = new Observable(subscriber => {
// 'Saving' the subscriber for when the observable is ready.
this.futureObservableSubscriber = subscriber;
// The returned function will be invoked when the below mentioned subject instance
// won't have any subscribers(after it had at least one).
return () => this.futureObservableSubscription.unsubscribe();
}).pipe(
// You can mimic the Subject behavior from your initial solution with the
// help of the `share` operator. What it essentially does it to *place*
// a Subject instance here and if multiple subscriptions occur, this Subject instance
// will keep track of all of them.
// Also, when the first subscriber is registered, the observable source(the Observable constructor's callback)
// will be invoked.
share()
);
private futureObservableSubscriber = null;
// We're using a subscription so that it's easier to collect subscriptions to this observable.
// It's also easier to unsubscribe from all of them at once.
private futureObservableSubscription = new Subscription();
constructor (/* ... */) {};
ngOnInit () {
// If you're using `share`, you're safe to have multiple subscribers.
// Otherwise, the Observable's callback(i.e `subscriber => {...}`) will be called multiple times.
futureObservable$.subscribe(/* ... */);
futureObservable$.subscribe(/* ... */);
}
whenObservableReady () {
const tim = timer(1000, 1000);
// Here we're adding the subscription so that is unsubscribed when the main observable
// is unsubscribed. This part can be found in the returned function from the Observable's callback.
this.futureObservableSubscription.add(tim.subscribe(this.futureObservableSubscriber));
}
};
Indeed, a possible downside is that you'll have to explicitly subscribe, e.g in the whenObservableReady method.
With this approach you can also have different sources:
whenAnotherObservableReady () {
// If you omit this, it should mean that you will have multiple sources at the same time.
this.cleanUpCrtSubscription();
const tim2 = timer(5000, 5000);
this.futureObservableSubscription.add(tim2.subscribe(this.futureObservableSubscriber));
}
private cleanUpCrtSubscription () {
// Removing the subscription created from the current observable(`tim`).
this.futureObservableSubscription.unsubscribe();
this.futureObservableSubscription = new Subscription();
}

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.

React Redux Action Creator Dispatch Issue with Fetch API and Promise

I am writing action creator in react app. where in when i do some api call i need to show the Progress Loader on screen. So, my action creator looks like this.
export const fetchData = (actionType, param) => (dispatch) => {
dispatch(Action(ActionConstants.SHOW_PROGRESS_LOADER)); // Show Loader Action
return fetchDataRequest(actionType, param) // Here is Fetch APi Call
.then(responseData => {
dispatch(Action(ActionConstants.HIDE_PROGRESS_LOADER));
dispatch(Action(recd(actionType), { data: responseData, receivedAt: Date.now() }));
}).catch((error) => {
dispatch(Action(ActionConstants.HIDE_PROGRESS_LOADER)); // Hide Loader Action
});
};
When i write this piece of code its working as expected, i am dispatching the action as dispatch(fetchData(data)) from component and i am able to show the loader in my Parent Component. What i understand is fetch is returning me the promise. Once the fetch gets completed then i am hiding the loader which is working as expected.
Now, There is scenario where in i need to do some validation where in i don't have to make any api call but all the validation are performed locally.
Here also i want to do the same thing like i need to show loader in my parent component as well when all the validation are done i need to hide the loader.
I have written the same piece of code even actions are getting called but my render function is not getting called.
My Code Looks like:
// This my action creator which will actually do the validation
export const validateAndSaveData = () => {
return ((dispatch, getState) => {
return new Promise((resolve, reject) => {
let saveRecommendDetailsFlag = true;
// here i am dispacthing some action and storing data in my store
saveRecommendDetailsFlag = canSaveData(getState());
if (saveRecommendDetailsFlag) {
resolve('SUCCESS');
} else {
reject('ERROR');
}
});
});
};
And there is one more action creator which i am calling it from from UI Component which will first initiate the show loader action and then perform validation and based on the result of validation i have to hide the loader.
export const saveData = () => {
return ((dispatch) => {
dispatch(Action(ActionConstants.SHOW_PROGRESS_LOADER)); // Show Loader Action
return dispatch(validateAndSaveData())
.then(() => {
// Here i m dispatching an action to do some more processing.
dispatch(Action(ActionConstants.HIDE_PROGRESS_LOADER)); // Hide Loader Action
})
.catch(() => {
dispatch(Action(ActionConstants.HIDE_PROGRESS_LOADER)); // Hide Loader Action
});
});
};
Everything is working fine but my loader are not coming on the screen. i am not able to figure it out where am i doing wrong.
Can anyone suggest something how can i solve this issue?
I got some workaround using setTimeout func but i don't think that is right approach.
export const saveData = () => {
return ((dispatch) => {
dispatch(Action(ActionConstants.SHOW_PROGRESS_LOADER)); // Show Loader Action
setTimeout(()=>return dispatch(validateAndSaveData())
.then(() => {
// Here i m dispatching an action to do some more processing.
dispatch(Action(ActionConstants.HIDE_PROGRESS_LOADER)); // Hide Loader Action
})
.catch(() => {
dispatch(Action(ActionConstants.HIDE_PROGRESS_LOADER)); // Hide Loader Action
});
},10);
});
};
Your code looks reasonable, my suspicion is that your validateAndSaveData promise finishes so quickly that there is no visible loader on the screen.
In that case, a timeout is totally reasonable. However, in order to do it properly, I would keep a state on if the loading screen is visible + if it's been shown long enough. You can then remove the loading screen once it is both up for long enough, and the actual event expires.
I'm not sure which action package you're using, so I can't post exact code, but the pseudocode would look something like this:
const delay = (seconds) => new Promise((resolve) => setTimeout(resolve, seconds));
let loadingCounter = 0;
const showLoadingScreen = () => (dispatch) => {
const counter = loadingCounter;
loadingCounter++;
delay(5).then(() => {
if (getStore().loadingScreen.counter === counter) {
dispatch(Action(ActionConstants.PROGRESS_LOADER_DELAY_ELAPSED))
}
})
return dispatch(Action(ActionConstants.SHOW_PROGRESS_LOADER, counter))
}
Basically, you would keep track of 3 pieces of state for the loader:
{
counter: 0,
taskCompleted: false,
canHide: false,
}
Counter is saved so that you can disambiguate what happens if you get SHOW_PROGRESS_LOADER while an existing SHOW_PROGRESS_LOADER is in progress.
taskCompleted keeps a record of whether the thing you're waiting on is done, and canHide keeps track if the loader has been visible on the screen long enough.
When you dispatch PROGRESS_LOADER_DELAY_ELAPSED it sets canHide to true, and when you dispatch HIDE_PROGRESS_LOADER it sets taskCompleted to true. (although you may want to rename the latter action). When both canHide and taskCompleted are set to true, only then can the loader go away.
This is a pretty common UI pattern - Try to complete a task quickly. If it takes more than a short amount of time, then throw up a loading dialog. However, the loading dialog is guaranteed to stay up a minimum amount of time to prevent flickering. So the more advanced version of this kind of pattern would be to add another state which doesn't show the progress loader at all unless the call takes more than Y milliseconds.
Hope this makes sense, leave a comment if not :-)

rxjs: how to evenly space rendering of responses via Observables

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>

Resources