RxJs How to complete inner observable - rxjs

I have function like this:
this.eventTaskWorking$ = completeStage
.pipe(
map(result => {
switch (result) {
case Statuses.LAST_TASK: {
console.info('returning finish event observable');
throw { err: 0 };
}
default: {
return EMPTY;
}
}
}),
catchError(() => completeEvent)
)
.subscribe();
When i throw an exception, "completeEvent" is completed, but if i try to use switchMap, mergeMap etc...it's not working:
this.eventTaskWorking$ = completeStage
.pipe(
map(result => {
switch (result) {
case Statuses.LAST_TASK: {
return completeEvent;
}
default: {
return EMPTY;
}
}
}),
switchMap(t => t),
)
.subscribe();
What's wrong?
UPD:
const completeEvent = this.FinishEvent(eventRef, uid);
private FinishEvent(eventRef: Observable<IEvent>, taskUid: string): Observable<any> {
return eventRef.pipe(
switchMap(t => this.UpdateTaskStatus(taskUid, 3)));
}

ok, seems FinishEvent didn't return observable, my fault

Related

Providing two combined Reducers for my redux saga store prevents my websocket channel message from triggering, but only one does not?

Configured my store this way with redux toolkit for sure
const rootReducer = combineReducers({
someReducer,
systemsConfigs
});
const store = return configureStore({
devTools: true,
reducer: rootReducer ,
// middleware: [middleware, logger],
middleware: (getDefaultMiddleware) => getDefaultMiddleware({ thunk: false }).concat(middleware),
});
middleware.run(sagaRoot)
And thats my channel i am connecting to it
export function createSocketChannel(
productId: ProductId,
pair: string,
createSocket = () => new WebSocket('wss://somewebsocket')
) {
return eventChannel<SocketEvent>((emitter) => {
const socket_OrderBook = createSocket();
socket_OrderBook.addEventListener('open', () => {
emitter({
type: 'connection-established',
payload: true,
});
socket_OrderBook.send(
`subscribe-asdqwe`
);
});
socket_OrderBook.addEventListener('message', (event) => {
if (event.data?.includes('bids')) {
emitter({
type: 'message',
payload: JSON.parse(event.data),
});
//
}
});
socket_OrderBook.addEventListener('close', (event: any) => {
emitter(new SocketClosedByServer());
});
return () => {
if (socket_OrderBook.readyState === WebSocket.OPEN) {
socket_OrderBook.send(
`unsubscribe-order-book-${pair}`
);
}
if (socket_OrderBook.readyState === WebSocket.OPEN || socket_OrderBook.readyState === WebSocket.CONNECTING) {
socket_OrderBook.close();
}
};
}, buffers.expanding<SocketEvent>());
}
And here's how my saga connecting handlers looks like
export function* handleConnectingSocket(ctx: SagaContext) {
try {
const productId = yield select((state: State) => state.productId);
const requested_pair = yield select((state: State) => state.requested_pair);
if (ctx.socketChannel === null) {
ctx.socketChannel = yield call(createSocketChannel, productId, requested_pair);
}
//
const message: SocketEvent = yield take(ctx.socketChannel!);
if (message.type !== 'connection-established') {
throw new SocketUnexpectedResponseError();
}
yield put(connectedSocket());
} catch (error: any) {
reportError(error);
yield put(
disconnectedSocket({
reason: SocketStateReasons.BAD_CONNECTION,
})
);
}
}
export function* handleConnectedSocket(ctx: SagaContext) {
try {
while (true) {
if (ctx.socketChannel === null) {
break;
}
const events = yield flush(ctx.socketChannel);
const startedExecutingAt = performance.now();
if (Array.isArray(events)) {
const deltas = events.reduce(
(patch, event) => {
if (event.type === 'message') {
patch.bids.push(...event.payload.data?.bids);
patch.asks.push(...event.payload.data?.asks);
//
}
//
return patch;
},
{ bids: [], asks: [] } as SocketMessage
);
if (deltas.bids.length || deltas.asks.length) {
yield putResolve(receivedDeltas(deltas));
}
}
yield call(delayNextDispatch, startedExecutingAt);
}
} catch (error: any) {
reportError(error);
yield put(
disconnectedSocket({
reason: SocketStateReasons.UNKNOWN,
})
);
}
}
After Debugging I got the following:
The Thing is that when I Provide one Reducer to my store the channel works well and data is fetched where as when providing combinedReducers I am getting
an established connection from my handleConnectingSocket generator function
and an empty event array [] from
const events = yield flush(ctx.socketChannel) written in handleConnectedSocket
Tried to clarify as much as possible
ok so I start refactoring my typescript by changing the types, then saw all the places that break, there was a problem in my sagas.tsx.
Ping me if someone faced such an issue in the future

Why doesn't EMPTY complete the observable?

In the code below, I am conditionally switching to another observable. If the condition is met it works fine. If the condition is not met and I return EMPTY from switchMap, the code in the subscribe block is not executed.
If I change return EMPTY to return of(x) it works.
this.claimStoreService.setProducts(this.claim.products)
.pipe(switchMap(x => {
if (this.incomeEligibility) {
return this.claimStoreService.saveIncomeEligibility();
} else {
return EMPTY;
}
}))
.subscribe(() => {
this.isSaving = false;
this.goIntoDisplayMode();
}, () => {
this.isSaving = false;
});
Try to use the third callback:
this.claimStoreService.setProducts(this.claim.products)
.pipe(switchMap(x => {
if (this.incomeEligibility) {
return this.claimStoreService.saveIncomeEligibility();
} else {
return EMPTY;
}
}))
.subscribe(
() => this.goIntoDisplayMode(),
console.err,
() => this.isSaving = false,
);
It is probably more clear if you pass to the subscribe function an Observer. Your code would become
this.claimStoreService.setProducts(this.claim.products)
.pipe(switchMap(x => {
if (this.incomeEligibility) {
return this.claimStoreService.saveIncomeEligibility();
} else {
return EMPTY;
}
}))
.subscribe({
next: () => this.goIntoDisplayMode(),
error: err => console.error(err),
complete: () => this.isSaving = false,
});

RXJS flatMap to repetitive observable

I'm trying to implement service, which provides observable if app has connection to my server or not, so when browser online, we ping server with timer. Here is code:
public get $connected(): Observable<boolean> {
return this.hasInternetConnection
.asObservable()
.pipe(
distinctUntilChanged(),
flatMap((connected: boolean) => {
if (!connected) {
return of(connected);
} else {
return timer(5000)
.pipe(
map(() => {
var success = Math.random() > 0.5;
console.log('PING: ' + success);
return success;
})
);
}
})
);
}
hasInternetConnection is just a BehaviorSubject bound to window online and offline events, timer emulates ping to my API server.
The issue is that my subscription $connected catches only first value from timer observable and then doesn't work. After hasInternetConnection subject changes to false and back to true, my subscription again gets first value and then nothing. Here is what I see in console:
PING: true
subscription tap
PING: true
PING: false
PING: true
...
How can I fix that? Thank you!
Full solution:
private hasInternetConnection: BehaviorSubject<boolean> = new BehaviorSubject<boolean>(navigator.onLine);
private connectedSubject: BehaviorSubject<boolean> = new BehaviorSubject<boolean>(true);
private recheckConnectionSubject: Subject<void> = new Subject<void>();
constructor(
private readonly http: HttpClient,
) {
fromEvent(window, 'online')
.pipe(takeUntil(this.destroyed))
.subscribe(() => {
this.hasInternetConnection.next(true);
});
fromEvent(window, 'offline')
.pipe(takeUntil(this.destroyed))
.subscribe(() => {
this.hasInternetConnection.next(false);
});
merge(
this.hasInternetConnection,
this.recheckConnectionSubject,
)
.pipe(
mapTo(this.hasInternetConnection.value),
switchMap((connected: boolean) => {
if (!connected) {
return of(connected);
} else {
return timer(0, 30000)
.pipe(
mergeMapTo(this.http.get(`${environment.apiRoot}/ping`, { responseType: 'text' })
.pipe(
map((res) => {
return true;
}),
catchError(() => {
return of(false);
})
)
),
);
}
})
)
.subscribe(this.connectedSubject);
}
public get $connected(): Observable<boolean> {
return this.connectedSubject.asObservable()
.pipe(
distinctUntilChanged(),
);
}
public resetTimer(): void {
this.recheckConnectionSubject.next();
}

RXJS listen to first subscription

I have a function that wraps observable with error handling, but to do so I need some code to run once it's inner observable is subscribed.
I also need that cancelling the higher Observable cancels the inner one, as it is doing HTTP call.
Context
slideshow: string[] = [];
currentIndex = 0;
private is = {
loading: new BehaviorSubject(false),
}
private loadImage(src: string): Observable;
private loadNextImage(index = this.currentIndex, preload = false): Observable<number> {
const nextIndex = (index + 1) % this.slideshow.length;
if (this.currentIndex == nextIndex) {
if (!preload) {
this.is.loading.next(false);
}
throw new Error('No other images are valid');
}
return ( possible code below )
}
Defer - This worked nicely until I realised this will create a new instance for every subscriber.
defer(() => {
if (!preload) {
this.is.loading.next(true);
}
return this.loadImage(this.slideshow[nextIndex]).pipe(
finalize(() => {
if (!preload) {
this.is.loading.next(false);
}
}),
map(() => nextIndex),
catchError(err => this.loadNextImage(nextIndex)),
);
});
Of(void 0).pipe(mergeMap(...)) - This does what is should, but it is really ugly
of(void 0).pipe(
mergeMap(() => {
if (!preload) {
this.is.loading.next(true);
}
return this.loadImage(this.slideshow[nextIndex]).pipe(
finalize(() => {
if (!preload) {
this.is.loading.next(false);
}
}),
map(() => nextIndex),
catchError(err => this.loadNextImage(nextIndex)),
);
}),
)
new Observable - I think there should be a solution that I am missing

Notify from inner flatMap

Here a quite complex sample:
Main:
this.runInstructionAndGetResult().subscribe({
next: val => console.log(`NEXT VALUE: ${val}`),
error: val => console.log(`ERROR VALUE: ${val}`),
complete: val => console.log(`COMPLETE`)
});
Observables:
public runInstructionAndGetResult(): Observable<string> {
return this.runAnInstruction()
.flatMap((data) => {
console.info("flatMap of runAnInstruction:", data);
return this.getInstructionExecutionStatusInPolling()
.filter(data => data != "Polling")
.take(1)
.flatMap((data) => {
console.info("flatMap of getInstructionExecutionStatusInPolling:", data);
return this.getInstructionResult();
}).map((data) => {
console.info("Map of getInstructionResult:", data);
return data;
});
});
}
public runAnInstruction(): Observable<string> {
return Observable.of("StartRun");
}
public getInstructionResult(): Observable<string> {
return Observable.of("FinalResult");
}
public getInstructionExecutionStatusInPolling(): Observable<string> {
return Observable.interval(1000)
.concatMap(data => {
return this.getInstructionExecutionStatus();
});
}
public getInstructionExecutionStatus(): Observable<string> {
return Observable.of("Polling", "Terminate");
}
Here plunk:
https://plnkr.co/edit/c1cahMtVARQnLgnHWlEe?p=preview
Main problem is that i just would like to be notify about "evolution" of inner stream outside.
Right now we have "next" event on main only when all inner flatMap are completed.
How to get notify? How can i emit explicit values to main stream for example during polling?
Thanks.
I found a solution to share.
Here plunker updated:
https://plnkr.co/edit/c1cahMtVARQnLgnHWlEe?p=preview
Basically i create a simple observable using : https://github.com/Reactive-Extensions/RxJS/blob/master/doc/api/core/operators/create.md
then i call programmatically next method and complete finally:
public runInstructionAndGetResult(): Observable<string> {
return Observable.create((ops)=> {
ops.next(1);
this.runAnInstruction()
.concatMap((data) => {
ops.next(2);
console.info("flatMap of runAnInstruction:", data);
return this.getInstructionExecutionStatusInPolling()
.filter(data => data != "Polling")
.take(1)
.concatMap((data) => {
ops.next(3);
console.info("flatMap of getInstructionExecutionStatusInPolling:", data);
return this.getInstructionResult();
}).map((data) => {
console.info("Map of getInstructionResult:", data);
ops.next(4);
ops.complete();
return data;
});
}).subscribe();
});
}

Resources