Nestjs #Sse : return result of a promise in rxjs observable - rxjs

I am trying to go a simple step beyond the nest doc example in implementing #Sse() in a controller but I never used rxjs untill now so Im a bit confused.
The flow is :
client send a POST request with a file payload
server (hopefully) sends back the newly created project with a prop status:UPLOADED
client subscribe to sse route described below passing as param the projectId it just received from server
in the meantime server is doingSomeStuff that could take from 10sec to a min. When doingSomeStuff is done, project status is updated in db from UPLOADED to PARSED
My need is for the #Sse decorated function to execute at x interval of time a "status-check" and return project.status (that may or may not have been updated at the time)
My present code :
#Sse('sse/:projectId')
sse(#Param('projectId') projectId: string): Observable<any> {
const projId$ = from(this.projectService.find(projectId)).pipe(
map((p) => ({
data: {
status: p.status,
},
})),
);
return interval(1000).pipe(switchMap(() => projId$));
}
I don't put code of the service here as it a simple mongooseModel.findById wrapper.
My problem is the status returned remains UPLOADED and is never updated. It doesnt seem the promise is reexecuted at every tick. If I console.log inside my service I can see my log being printed only once with the initial project value while I expect to see a new log at each tick.

This is a two-step process.
We create an observable out of the promise generated by this.service.findById() using the from operator in rxjs. We also use the map operator to set the format of the object we need when someone subscribes to this observable.
We want to return this observable every x seconds. interval(x) creates an observable that emits a value after every x milliseconds. Hence, we use this and then switchMap to the projId$ whenever the interval emits a value. The switchMap operator switches to the inner observable whenever the outer observable emits a value.
Please note: Since your server may take 10 sec, to min for doing the operation, you should set the intervalValue accordingly. In the code snippet below, I've set it to 10,000 milli seconds which is 10 seconds.
const intervalValue = 10000;
#Sse('sse/:projectId')
sse(#Param('projectId') projectId: string): Observable < any > {
return interval(intervalValue).pipe(
switchMap(() => this.projectService.find(projectId)),
map((p) => ({
data: {
status: p.status,
}
})));
}
// OR
#Sse('sse/:projectId')
sse(#Param('projectId') projectId: string): Observable < any > {
const projId$ = defer(() => this.service.findById(projectId)).pipe(
map(() => ({
data: {
_: projectId
}
}))
);
return interval(intervalValue).pipe(switchMap(() => projId$));
}

#softmarshmallow
You can watch model changes and use observable stream to send it.
Something like this
import { Controller, Param, Sse } from '#nestjs/common'
import { filter, map, Observable, Subject } from 'rxjs'
#Controller('project')
export class ProjectStatusController {
private project$ = new Subject()
// watch model event
// this method should be called when project is changed
onProjectChange(project) {
this.project$.next(project)
}
#Sse('sse/:projectId')
sse(#Param('projectId') projectId: string): Observable<any> {
return this.project$.pipe(
filter((project) => project.projectId === projectId),
map((project) => ({
data: {
status: project.status,
},
})),
)
}
}

Related

Why distinctUntilChanged won't work in asyncValidators in angular

When I press a key on a form input for some reason the async validator doesn't detect the distinctUntilChanged and it still sends API requests.
for example if I press 35, delete 5 and after that add 5 again it still sends the request.
this is the code:
(I've tried pretty much everything but still doesn't work)
validateSomeNumber(control: FormControl): Observable<any> | Promise <any> {
this.isSubmitBtnDisabled = true;
return control.valueChanges.pipe(
debounceTime(1000),
distinctUntilChanged(),
switchMap((value) => {
return this.apiService.someApiRequest({ 'to_number': control.value }).pipe(
map(res => {
console.log(res);
if (res.success) {
// console.log(res);
this.isSubmitBtnDisabled = false;
return null;
} else {
// console.log(res);
this.isSubmitBtnDisabled = true;
return{ 'invalidCharacters': true };
}
}),
);
}),
first()
);
}
By default validateSomeNumber is called after every value change.
If you return this on every value change
return control.valueChanges.pipe(
debounceTime(1000),
distinctUntilChanged(),
...
)
you're creating a new Observable of value changes on every value change. e.g if you type four characters you end up with four independent Observables each emitting one character and not with one Observable emitting four times. So debounceTime and distinctUntilChanged will only effecting the Observable you create on a particular value change but not the value change process as a whole. If they only effect an Observable that emits once they obviously don't work as you intend them to do.
You should return the http request directly
validateSomeNumber(control: FormControl): Observable<any> | Promise <any> {
this.isSubmitBtnDisabled = true;
return this.apiService.someApiRequest({ 'to_number': control.value }).pipe(
map(..),
);
}
Limiting the request frequency
Option 1: updateOn
To prevent the http request from being executed on every value change Angular recommends changing the updateOn property to submit or blur.
With template-driven forms:
<input [(ngModel)]="name" [ngModelOptions]="{updateOn: 'blur'}">
With reactive forms:
new FormControl('', {updateOn: 'blur'});
{updateOn: 'blur'} will only execute the validators when your input looses focus.
Option 2: Emulate debounceTime and distinctUntilChanged
Angular automatically unsubscribes from the previous Observable returned by the AsyncValidator if the form value changes. This allows you to emulate debounceTime with timer. To emulate distinctUntilChanged you can keep track of the last request term and do the equality check yourself.
private lastRequestTerm = null;
validateSomeNumber(control: FormControl): Observable<any> | Promise <any> {
this.isSubmitBtnDisabled = true;
// emulate debounceTime
return timer(1000).pipe(
// emulate distinceUntilChanged
filter(_ => control.value != this.lastRequestTerm),
switchMap(() => {
this.lastSearchTerm = control.value;
return this.apiService.someApiRequest({ 'to_number': control.value });
}),
map(..)
);
}

Recursive observable

I'm working with RxJs and I have to make a polling mechanism to retrieve updates from a server.
I need to make a request every second, parse the updates, emit it and remember its id, because I need it to request the next pack of updates like getUpdate(lastId + 1).
The first part is easy so I just use interval with mergeMap
let lastId = 0
const updates = Rx.Observable.interval(1000)
.map(() => lastId)
.mergeMap((offset) => getUpdates(offset + 1))
I'm collecting identifiers like this:
updates.pluck('update_id').scan(Math.max, 0).subscribe(val => lastId = val)
But this solution isn't pure reactive and I'm looking for the way to omit the usage of "global" variable.
How can I improve the code while still being able to return observable containing just updates to the caller?
UPD.
The server response for getUpdates(id) looks like this:
[
{ update_id: 1, payload: { ... } },
{ update_id: 3, payload: { ... } },
{ update_id: 2, payload: { ... } }
]
It may contain 0 to Infinity updates in any order
Something like this? Note that this is an infinite stream since there is no condition to abort; you didn't give one.
// Just returns the ID as the update_id.
const fakeResponse = id => {
return [{ update_id: id }];
};
// Fakes the actual HTTP call with a network delay.
const getUpdates = id => Rx.Observable.of(null).delay(250).map(() => fakeResponse(id));
// Start with update_id = 0, then recursively call with the last
// returned ID incremented by 1.
// The actual emissions on this stream will be the full server responses.
const updates$ = getUpdates(0)
.expand(response => Rx.Observable.of(null)
.delay(1000)
.switchMap(() => {
const highestId = Math.max(...response.map(update => update.update_id));
return getUpdates(highestId + 1);
})
)
updates$.take(5).subscribe(console.log);
<script src="https://cdnjs.cloudflare.com/ajax/libs/rxjs/5.5.6/Rx.js"></script>
To define the termination of the stream, you probably want to hook into the switchMap at the end; use whatever property of response to conditionally return Observable.empty() instead of calling getUpdates again.

How to test an inner Observable that will not complete?

I'm using jest to test a redux-observable epic that forks off an inner observable created using Observable.fromEvent and listens for a specific keypress before emitting an action.
I'm struggling to test for when the inner Observable does not receive this specific keypress and therefore does not emit an action.
Using jest, the following times out:
import { Observable, Subject } from 'rxjs'
import { ActionsObservable } from 'redux-observable'
import keycode from 'keycode'
const closeOnEscKeyEpic = action$ =>
action$.ofType('LISTEN_FOR_ESC').switchMapTo(
Observable.fromEvent(document, 'keyup')
.first(event => keycode(event) === 'esc')
.mapTo({ type: 'ESC_PRESSED' })
)
const testEpic = ({ setup, test, expect }) =>
new Promise(resolve => {
const input$ = new Subject()
setup(new ActionsObservable(input$))
.toArray()
.subscribe(resolve)
test(input$)
}).then(expect)
// This times out
it('no action emitted if esc key is not pressed', () => {
expect.assertions(1)
return testEpic({
setup: input$ => closeOnEscKeyEpic(input$),
test: input$ => {
// start listening
input$.next({ type: 'LISTEN_FOR_ESC' })
// press the wrong keys
const event = new KeyboardEvent('keyup', {
keyCode: keycode('p'),
})
const event2 = new KeyboardEvent('keyup', {
keyCode: keycode('1'),
})
global.document.dispatchEvent(event)
global.document.dispatchEvent(event2)
// end test
input$.complete()
},
expect: actions => {
expect(actions).toEqual([])
},
})
})
My expectation was that calling input$.complete() would cause the promise in testEpic to resolve, but for this test it does not.
I feel like I'm missing something. Does anyone understand why this is not working?
I'm still new to Rx/RxJS, so my apologies if the terminology of this answer is off. I was able to reproduce your scenario, though.
The inner observable (Observable.fromEvent) is blocking the outer observable. The completed event on your ActionsObservable doesn't propagate through until after the inner observable is completed.
Try out the following code snippet with this test script:
Run the code snippet.
Press a non-Escape key.
Nothing should be printed to the console.
Select the "Listen for Escape!" button.
Press a non-Escape key.
The keyCode should be printed to the console.
Select the "Complete!" button.
Press a non-Escape key.
The keyCode should be printed to the console.
Press the Escape key.
The keyCode should be printed to the console
The onNext callback should print the ESC_PRESSED action to the console.
The onComplete callback should print to the console.
document.getElementById('complete').onclick = onComplete
document.getElementById('listenForEsc').onclick = onListenForEsc
const actions = new Rx.Subject()
const epic = action$ =>
action$.pipe(
Rx.operators.filter(action => action.type === 'LISTEN_FOR_ESC'),
Rx.operators.switchMapTo(
Rx.Observable.fromEvent(document, 'keyup').pipe(
Rx.operators.tap(event => { console.log('keyup: %s', event.keyCode) }),
Rx.operators.first(event => event.keyCode === 27), // escape
Rx.operators.mapTo({ type: 'ESC_PRESSED' }),
)
)
)
epic(actions.asObservable()).subscribe(
action => { console.log('next: %O', action) },
error => { console.log('error: %O', error) },
() => { console.log('complete') },
)
function onListenForEsc() {
actions.next({ type: 'LISTEN_FOR_ESC' })
}
function onComplete() {
actions.complete()
}
<script src="https://unpkg.com/rxjs#5.5.0/bundles/Rx.min.js"></script>
<button id="complete">Complete!</button>
<button id="listenForEsc">Listen for Escape!</button>
Neither the switchMapTo marble diagram nor its textual documentation) clearly indicate what happens when the source observable completes before the inner observable. However, the above code snippet demonstrates exactly what you observed in the Jest test.
I believe this answers your "why" question, but I'm not sure I have a clear solution for you. One option could be to hook in a cancellation action and use takeUntil on the inner observable. But, that might feel awkward if that's only ever used in your Jest test.
I can see how this epic/pattern wouldn't be a problem in a real application as, commonly, epics are created and subscribed to once without ever being unsubscribed from. However, depending on the specific scenario (e.g. creating/destroying the store multiple times in a single application), I could see this leading to hung subscriptions and potential memory leaks. Good to keep in mind!

RxJS and redux-observable: delay(time) is not applied correctly after mapTo()

I am trying to dispatch 'SET_MIDDLEWARE_TYPE' action with payload.type = 'observable', wait 5 seconds and then make an API call. Currently, the SET_MIDDLEWARE_TYPE action is not being dispatched. If I remove the delay and the mergeMap, it dispatch that action.
Expected:
FETCH_USER_WITH_OBSERVABLE_REQUEST --> SET_MIDDLEWARE_TYPE (wait 5 seconds) --> FETCH_USER_WITH_OBSERVABLE_SUCCESS
Actual:
FETCH_USER_WITH_OBSERVABLE_REQUEST --> (wait 5 seconds) --> FETCH_USER_WITH_OBSERVABLE_SUCCESS
How can I obtained the expected behavior?
Code:
import { Observable } from 'rxjs';
import { githubApi } from './api';
const githubEpic = action$ =>
// Take every request to fetch a user
action$.ofType('FETCH_USER_WITH_OBSERVABLES_REQUEST')
.mapTo(({ type: 'SET_MIDDLEWARE_TYPE', payload: { type: 'observable'} }))
// Delay execution by 5 seconds
.delay(5000)
// Make API call
.mergeMap(action => {
// Create an observable for the async call
return Observable.from(githubApi.getUser('sriverag'))
// Map the response to the SUCCESS action
.map((result) => {
return {
type: 'FETCH_USER_WITH_OBSERVABLES_SUCCESS',
payload: { result } ,
};
});
});
export default githubEpic;
You are abandoning the SET_MIDDLEWARE_TYPE action when you perform the mergeMap. Notice the parameter that you pass into the mergeMap called action? That cannot make it any further downstream unless you propagate it.
You will need to prepend it to the outgoing stream. I suggest you do the following instead:
import { Observable } from 'rxjs';
import { githubApi } from './api';
const githubEpic = action$ =>
// Take every request to fetch a user
action$.ofType('FETCH_USER_WITH_OBSERVABLES_REQUEST')
.mapTo(({ type: 'SET_MIDDLEWARE_TYPE', payload: { type: 'observable'} }))
// Delay execution by 5 seconds
.delay(5000)
// Make API call
.mergeMap(action =>
// Async call
Observable.from(githubApi.getUser('sriverag'))
// Map the response to the SUCCESS action
.map(result => ({
type: 'FETCH_USER_WITH_OBSERVABLES_SUCCESS',
payload: { result }
}))
// Prepend the original action to your async stream so that it gets
// forwarded out of the epic
.startWith(action)
);
export default githubEpic;

switchMap does not seem to complete when the inner observable completes

I'm still a noob when it comes to RxJS but here's a JSBin of what I am trying to do.
https://jsbin.com/wusalibiyu/1/edit?js,console
I have an observable 'a' (in my case it's the current active connection) which emits a new object whenever the connection reconnects. It's an observable itself because it can re-emit a new value.
I now want an observable that completes whenever an action is executed on the current connection. That action notifies it is done when the observable for it completes. This is b.
The problem is that when the inner observable completes, the outer does not complete. How to make the outer observable complete ... . Is there a different operator I should be using in RxJS5?
If I understand your requirement correctly, you can "lift" the inner stream out using a materialize/dematerialize pair (note I refactored as well as part of my never ending war to get people to stop using Observable#create).
JsBin (excerpt below)
function b(a) {
// Emit and complete after 100 millis
return Rx.Observable.timer(100)
// Ignore any values emitted
.ignoreElements()
// Emit the value on start
.startWith(a)
.do(() => console.log('creating observable'))
.finally(() => console.log('b done'));
}
var a$ = Rx.Observable.from(['a', 'b'])
.finally(() => console.log('a done'));
var result$ = a$.switchMap(function(a) {
console.log('switching map for a to b', a);
// This "materializes" the stream, essentially it maps complete -> next
return b(a).materialize();
})
// This does the opposite, and converts complete events back,
// but since we are now in the outer stream
// this results in the outer stream completing as well.
.dematerialize()
.share();
result$.subscribe(function(value) {
console.log('value', value);
}, function(e) {
console.error('e', e);
}, function() {
console.log('completed!');
})
result$.toPromise().then(function(data) {
console.log('this should trigger!?', data);
}, function(e) {
console.error('boom', e.toString());
});

Resources