Rxjs create an observable that emits when a function is called - rxjs

I'm likely missing something simple but can't quite puzzle this out from the docs. I'd simply like to have a function that when called will emit a value on an rxjs observable.
Psuedocode:
const myFunction = () => true //or whatever value
const myObservable = ... emit true whenever myFunction is called
...use myFunction somewhere where it being called is a useful event so
observers of myObservable can process it
What's the standard pattern to emit a value when a function is called with rxjs?

You need a Subject for that https://www.learnrxjs.io/learn-rxjs/subjects
Here is the reworked example from the documentation:
import { Subject } from 'rxjs';
const myObservable = new Subject<number>();
const myFunction = () => {
subject.next(1);
subject.next(2);
}
myObservable.subscribe({
next: (v) => console.log(`observerA: ${v}`)
});
myObservable.subscribe({
next: (v) => console.log(`observerB: ${v}`)
});
// ...use myFunction somewhere where it being called is a useful event so
myFunction();
Normally, you don't even need myFunction. Calling subject.next() would be enough.

You need to use Subject.
const subject = new Subject(); // a subject to notify
const myObservable = subject.asObservable();
const myFunction = () => {
subject.next(true);
};
myObservable.subscribe(console.log);
myFunction();
myFunction();
myFunction();

Related

Tauri: Convert listen(eventName, handler) to an Observable to be handled in Angular

If you're using JS, the documentation works well. But in case of angular I would prefer to handle observables instead of promises. The problem is that this kind of promise has a handler. I tried many approaches listed below but nothing seems to work.
from(listen("click", v => v))
let x = async() => listen("click", v => v)
Does anyone know how to convert this kind of event to an Observable?
The response is always this:
function () {
var self = this,
args = arguments;
return new Promise(function (resolve, reject) {
var gen = fn.apply(self, args);
function _next(value) {
asyncGeneratorStep(gen, resolve, reject, _next, _throw, "next", value);
}
function _throw(err) {
asyncGeneratorStep(gen, resolve, reject, _next, _throw, "throw", err);
}
_next(undefined);
});
}
You would have to create an Observable yourself with new Observable.
const obs$ = new Observable((subscriber) => {
const unlisten = listen("click", v => subscriber.next(v))
return async () => {
(await unlisten)()
}
})
Inside the callback, we listen to the events and pass each event to subscriber.next(v).
We also want to call unlisten when the Observable is unsubscribed to clean up the event listener. We can do that by returning the unlisten. The function returned by the callback will be called when the Observable is unsubscribed.
Thanks to #Tobias S., I was able to create those 2 functions and reuse them in all my services.
import {from, map, Observable, ObservableInput, ObservedValueOf} from "rxjs";
import {emit, listen, Event} from "#tauri-apps/api/event";
export function tauriListen(listenerName: string): Observable<any> {
return new Observable<any>((subscriber) => {
// return from(listen(listenerName, v => subscriber.next(v))).subscribe()
const unlisten = listen(listenerName, v => subscriber.next(v))
return async () => {
(await unlisten)()
}
}).pipe(
map((response: Event<any>) => response.payload)
);
}
export function tauriEmit(emitterName: string, payload: any) {
return from(emit(emitterName, payload));
}

Cancellation on unsubscribe only if observable hasn't completed

I have an observable for which I would want to call cancellation (teardown) logic when subscriber unsubscribes from it but only if the source observable haven't completed yet (or failed) by itself.
The built-in finalize operator lets to register custom callback when unsubscribe occurs but it being called whenever the unsubscription was caused by subscriber or completion of the source observable.
I implemented the this helper function:
function withCancellation(source, onCancel) {
return new Observable(subscriber => {
let completed = false;
const cancellable = source.pipe(
tap({
error: () => { completed = true; },
complete: () => { completed = true; },
})
);
const subscription = cancellable.subscribe(subscriber);
subscription.add(() => { if (!completed) onCancel(); });
return subscription;
});
}
Which I can use the following way:
const sourceStream = startJob(jobId); // returns source observable
const cancellableStream = withCancellation(sourceStream, () => stopJob(jobId));
Is there any more concise way to achieve the same using any built-in primitives?

Any reason to use shareReplay(1) in a BehaviorSubject pipe?

I'm using a library that exposes data from a service class using a pretty common BehaviorSubject pattern. The only notable difference with the implementation and what I have seen/used myself is the addition of a pipe with a shareReplay(1) operator. I'm not sure if the shareReplay is required. What effect, if any, does the shareReplay have in this case?
// "rxjs": "^6.3.0"
this.data = new BehaviorSubject({});
this.data$ = this.data.asObservable().pipe(
shareReplay(1)
)
Note: I've read a number of articles on shareReplay, and I've seen questions about different combinations of shareReplay and Subject, but not this particular one
Not in your example but imagine if there was some complex logic in a map function that transformed the data then the share replay would save that complex logic being run for each subscription.
const { BehaviorSubject } = rxjs;
const { map, shareReplay } = rxjs.operators;
const bs$ = new BehaviorSubject('initial value');
const obs$ = bs$.pipe(
map(val => {
console.log('mapping');
return 'mapped value';
}),
shareReplay({bufferSize:1, refCount: true})
);
obs$.subscribe(val => { console.log(val); });
obs$.subscribe(val => { console.log(val); });
<script src="https://cdnjs.cloudflare.com/ajax/libs/rxjs/6.5.1/rxjs.umd.min.js"></script>
Compare without the share, the map happens twice.
const { BehaviorSubject } = rxjs;
const { map } = rxjs.operators;
const bs$ = new BehaviorSubject('initial value');
const obs$ = bs$.pipe(
map(val => {
console.log('mapping');
return 'mapped value';
})
);
obs$.subscribe(val => { console.log(val); });
obs$.subscribe(val => { console.log(val); });
<script src="https://cdnjs.cloudflare.com/ajax/libs/rxjs/6.5.1/rxjs.umd.min.js"></script>
With this pattern (use of shareReplay(1)), the service protects itself from the user using the next() function of the BehaviorSubject while sending the last value of the BehaviorSubject (which would not have been the case without shareReplay(1)).

Turn observable into subject

We have a function that gets a stream from the backend as observable. However we would like to be able to push to that observable as well to see the changes before those are done in the back-end. To do so I tried giving back a subject instead but the connection is still on going after the unsubscribe.
In other words, in the code below, we would like the console.log(i) not to start before we subscribe to the subject, and finish when we unsubscribe from it :
import { ReplaySubject, Observable, interval } from 'rxjs';
import { tap } from 'rxjs/operators'
function test() {
const obs = interval(1000).pipe(tap(i => console.log(i)));
const subj = new ReplaySubject(1);
obs.subscribe(subj);
return subj;
}
const subject = test();
subject.next('TEST');
const subscription = subject.pipe(
tap(i => console.log('from outside ' + i))
).subscribe()
setTimeout(_ => subscription.unsubscribe(), 5000);
example
You cannot subscribe in test. I guess you want to create an Observable and a Subject and merge those - you would have to return both separately.
return [subject, merge(subject, obs)]
and then
const [subject, obs] = test();
subject.next()
But I would do it by providing subject as a parameter.
import { ReplaySubject, Observable, interval, merge } from 'rxjs';
import { tap } from 'rxjs/operators'
function test(subject) {
return merge(
interval(1000).pipe(tap(i => console.log(i))),
subject
);
}
const subject = new ReplaySubject(1);
const obs = test(subject);
subject.next('TEST');
const subscription = obs.pipe(
tap(i => console.log('from outside ' + i))
).subscribe()
setTimeout(_ => subscription.unsubscribe(), 5000);

RxJS: Conditional map or mergeMap

If I have a method that sometimes returns a string and sometimes returns a promise that results in a string how can I chain that in my observable?
Example
function doSomething() {
if (Math.random() < 0.5) {
return 'test';
}
return new Promise(resolve => resolve('test'));
}
const example = of(undefined).pipe(
mergeMap(() => doSomething())
);
const subscribe = example.subscribe(val => console.log(val));
I would like to always log "test".
One way of solving the problem would be to make the function passed to mergeMap an async function and to await the return value of doSomething, like this:
const example = of(undefined).pipe(
mergeMap(async () => await doSomething())
);
You can do this because mergeMap expects the function to return an ObservableInput and a promise is a valid ObservableInput.
Doing so takes advantage of that fact await will accept either a promise or an ordinary value - to which the promise returned by the async function will resolve.
function doSomething() {
if (Math.random() < 0.5) {
return 'test';
}
return new Promise(resolve => resolve('test'));
}
const example = of(undefined)
.pipe(
map(() => doSomething()),
mergeMap(value => value instanceof Promise ? from(value) : from([value]))
);
const subscribe = example.subscribe(val => console.log(val));
Not that you asked, but your code is an anti-pattern. Your doSomething method should always return the same value type, in your case, either a string or a promise.

Resources