I have a question how do you wrap a normal sync code inside observable. Let's take this example:
let isCalling = false;
function makeHttpCall(url) {
isCalling = true;
return rxJsFetch(url).pipe(tap(() => (isCalling = false)));
}
Problem with that function is that isCalling flag will be set no matter if someone subscribes or not. I don't want that (becuase it is not calling yet). To combat that I usually do something like:
let isCalling = false;
function makeHttpCallWrapped(url) {
return of(null).pipe(
mergeMap(() => {
isCalling = true;
return rxJsFetch(url).pipe(tap(() => (isCalling = false)));
}),
);
}
And that works, isCalling is set only after subscribe.
Problem with that code is that it is not very elegant, nor clear to someone starting with RxJs. My question to you is, how do you handle that? Is there something in RxJs that I don't know about that handles it more gracefully?
RxJS Defer Operator
Defer lets you create an observable upon subscription. It's exactly a cleaner version of what you're already doing.
let isCalling = false;
function makeHttpCall(url) {
return defer(() => {
isCalling = true;
return rxJsFetch(url).pipe(tap(() => (isCalling = false)));
});
}
To make it more graceful I create a helper method fromFunction:
import { of } from 'rxjs';
import { mergeMap } from 'rxjs/operators';
import { SubscribableOrPromise } from 'rxjs/src/internal/types';
export function fromFunction<T>(func: () => SubscribableOrPromise<T>) {
return of(undefined).pipe(mergeMap(func));
}
And then use it like:
let isCalling = false;
function makeHttpCallWrapped(url) {
return fromFunction(() => {
isCalling = true;
return rxJsFetch(url).pipe(tap(() => (isCalling = false)));
});
}
I kind of feel like from should also accept input like that.
Related
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));
}
I'm using RxJS 6 to lazily step through iterable objects using code similar to example running below. This is working well but I'm having trouble solving my final use case.
Full code here
import { EMPTY, defer, from, of } from "rxjs";
import { delay, expand, mergeMap, repeat } from "rxjs/operators";
function stepIterator (iterator) {
return defer(() => of(iterator.next())).pipe(
mergeMap(result => result.done ? EMPTY : of(result.value))
);
}
function iterateValues ({ params }) {
const { values, delay: delayMilliseconds } = params;
const isIterable = typeof values[Symbol.iterator] === "function";
// Iterable values which are emitted over time are handled manually. Otherwise
// the values are provided to Rx for resolution.
if (isIterable && delayMilliseconds > 0) {
const iterator = values[Symbol.iterator]();
// The first value is emitted immediately, the rest are emitted after time.
return stepIterator(iterator).pipe(
expand(v => stepIterator(iterator).pipe(delay(delayMilliseconds)))
);
} else {
return from(values);
}
}
const options = {
params: {
// Any iterable object is walked manually. Otherwise delegate to `from()`.
values: ["Mary", "had", "a", "little", "lamb"],
// Delay _between_ values.
delay: 350,
// Delay before the stream restarts _after the last value_.
runAgainAfter: 1000,
}
};
iterateValues(options)
// Is not repeating?!
.pipe(repeat(3))
.subscribe(
v => {
console.log(v, Date.now());
},
console.error,
() => {
console.log('Complete');
}
);
I'd like to add in another option which will re-execute the stream, an indefinite number of times, after a delay (runAgainAfter). I'm having trouble composing this in cleanly without factoring the result.done case deeper. So far I've been unable to compose the run-again behavior around iterateValues.
What's the best approach to accomplish the use case?
Thanks!
Edit 1: repeat just hit me in the face. Perhaps it means to be friendly.
Edit 2: No, repeat isn't repeating but the observable is completing. Thanks for any help. I'm confused.
For posterity here is the full code sample for a revised edition is repeat-able and uses a consistent delay between items.
import { concat, EMPTY, defer, from, interval, of, throwError } from "rxjs";
import { delay, expand, mergeMap, repeat } from "rxjs/operators";
function stepIterator(iterator) {
return defer(() => of(iterator.next())).pipe(
mergeMap(result => (result.done ? EMPTY : of(result.value)))
);
}
function iterateValues({ params }) {
const { values, delay: delayMilliseconds, times = 1 } = params;
const isIterable =
values != null && typeof values[Symbol.iterator] === "function";
if (!isIterable) {
return throwError(new Error(`\`${values}\` is not iterable`));
}
// Iterable values which are emitted over time are handled manually. Otherwise
// the values are provided to Rx for resolution.
const observable =
delayMilliseconds > 0
? defer(() => of(values[Symbol.iterator]())).pipe(
mergeMap(iterator =>
stepIterator(iterator).pipe(
expand(v => stepIterator(iterator).pipe(delay(delayMilliseconds)))
)
)
)
: from(values);
return observable.pipe(repeat(times));
}
I'm gonna be honest, but there could be better solution for sure. In my solution, I ended up encapsulating delay logic in a custom runAgainAfter operator. Making it an independent part, that doesn't affect your code logic directly.
Full working code is here
And the code of runAgainAfter if anybody needs it:
import { Observable } from "rxjs";
export const runAgainAfter = delay => observable => {
return new Observable(observer => {
let timeout;
let subscription;
const subscribe = () => {
return observable.subscribe({
next(value) {
observer.next(value);
},
error(err) {
observer.error(err);
},
complete() {
timeout = setTimeout(() => {
subscription = subscribe();
}, delay);
}
});
};
subscription = subscribe();
return () => {
subscription.unsubscribe();
clearTimeout(timeout);
};
});
};
Hope it helps <3
I need to create an RxJS Observable such that it returns a value when call back function completes.
Below is the code, I have tried.
I want to return 'resources' to be returned in the caller subscribing to loadMarkerImages function
loadMarkerImages(markerNameAndImageUrlMap) {
let loader = new PIXI.loaders.Loader();
for (let markerKey in markerNameAndImageUrlMap) {
let imageUrl = markerNameAndImageUrlMap[markerKey];
loader.add(markerKey, imageUrl);
}
Observable.create()
return defer(() => {
loader.load((loader, resources) => {
return of(resources);
});
})
}
See the documentation for how to create an observable:
return new Observable(subscriber => {
let loader = new PIXI.loaders.Loader();
for (let markerKey in markerNameAndImageUrlMap) {
let imageUrl = markerNameAndImageUrlMap[markerKey];
loader.add(markerKey, imageUrl);
}
loader.load((loader, resources) => {
subscriber.next(resources);
subscriber.complete();
});
}
Make sure to also handle the error case if the loader.load() call can fail, though. Otherwise the returned observable will never emit, never complete, and never error.
I was trying to return filter function but return doesn't seem to work with callbacks. Here this.store.let(getIsPersonalized$) is an observable emitting boolean values and this.store.let(getPlayerSearchResults$) is an observable emiting objects of video class.
How do I run this synchronously, can I avoid asynchronus callback altogether given that I can't modify the observables received from store.
isPersonalized$ = this.store.let(getIsPersonalized$);
videos$ = this.store.let(getPlayerSearchResults$)
.map((vids) => this.myFilter(vids));
myFilter(vids) {
this.isPersonalized$.subscribe((x){
if(x){
return this.fileterX(vids);//Return from here
}
else {
return this.filterY(vids);//Or Return from here
}
});
}
fileterX(vids) {
return vids.filter((vid) => vids.views>100;);
}
fileterY(vids) {
return vids.filter((vid) => vids.views<20;);
}
I got it working this way, you don't need myFilter(vids) at all if you can get the branching out on isPersonalized$'s subscribe. Here is the updated code.
this.store.let(getIsPersonalized$);
videos$: Observable<any>;
ngOnInit() {
this.isPersonalized$.subscribe((x) => {
if (x) {
this.videos$ = this.store.let(getPlayerSearchResults$)
.map((vids) => this. fileterX(vids));
} else {
this.videos$ = this.store.let(getPlayerSearchResults$)
.map((vids) => this. fileterY(vids));
}
});
}
fileterX(vids) {
return vids.filter((vid) => vids.views>100;);
}
fileterY(vids) {
return vids.filter((vid) => vids.views<20;);
}
It looks like you want to evaluate the latest value of isPersonalized$ within the map function, i'd do that via withLatestFrom (Example: The first one toggles true/false every 5s, the second emits an increasing number every 1s):
const isPersonalized$ = Rx.Observable.interval(5000)
.map(value => value % 2 === 0);
const getPlayerSearchResults$ = Rx.Observable.interval(1000)
.withLatestFrom(isPersonalized$)
.map(bothValues => {
const searchResult = bothValues[0];
const isPersonalized = bothValues[1];
...
});
When creating an Rx.Subject using Subject.create(observer, observable), the Subject is so lazy. When I try to use subject.onNext without having a subscription, it doesn't pass messages on. If I subject.subscribe() first, I can use onNext immediately after.
Let's say I have an Observer, created like so:
function createObserver(socket) {
return Observer.create(msg => {
socket.send(msg);
}, err => {
console.error(err);
}, () => {
socket.removeAllListeners();
socket.close();
});
}
Then, I create an Observable that accepts messages:
function createObservable(socket) {
return Observable.fromEvent(socket, 'message')
.map(msg => {
// Trim out unnecessary data for subscribers
delete msg.blobs;
// Deep freeze the message
Object.freeze(msg);
return msg;
})
.publish()
.refCount();
}
The subject is created using these two functions.
observer = createObserver(socket);
observable = createObservable(socket);
subject = Subject.create(observer, observable);
With this setup, I'm not able to subject.onNext immediately (even if I don't care about subscribing). Is this by design? What's a good workaround?
These are actually TCP sockets, which is why I haven't relied on the super slick websocket subjects.
The basic solution, caching nexts before subscription with ReplaySubject:
I think all you wanted to do is use a ReplaySubject as your observer.
const { Observable, Subject, ReplaySubject } = Rx;
const replay = new ReplaySubject();
const observable = Observable.create(observer => {
replay.subscribe(observer);
});
const mySubject = Subject.create(replay, observable);
mySubject.onNext(1);
mySubject.onNext(2);
mySubject.onNext(3);
mySubject.subscribe(x => console.log(x));
mySubject.onNext(4);
mySubject.onNext(5);
Results in:
1
2
3
4
5
A socket implementation (example, don't use)
... but if you're looking at doing a Socket implementation, it gets a lot more complicated. Here is a working socket implementation, but I don't recommend you use it. Rather, I'd suggest that you use one of the community supported implementations either in rxjs-dom (if you're an RxJS 4 or lower) or as part of RxJS 5, both of which I've helped work on.
function createSocketSubject(url) {
let replay = new ReplaySubject();
let socket;
const observable = Observable.create(observer => {
socket = new WebSocket(url);
socket.onmessage = (e) => {
observer.onNext(e);
};
socket.onerror = (e) => {
observer.onError(e);
};
socket.onclose = (e) => {
if (e.wasClean) {
observer.onCompleted();
} else {
observer.onError(e);
}
}
let sub;
socket.onopen = () => {
sub = replay.subscribe(x => socket.send(x));
};
return () => {
socket && socket.readyState === 1 && socket.close();
sub && sub.dispose();
}
});
return Subject.create(replay, observable);
}
const socket = createSocketSubject('ws://echo.websocket.org');
socket.onNext('one');
socket.onNext('two');
socket.subscribe(x => console.log('response: ' + x.data));
socket.onNext('three');
socket.onNext('four');
Here's the obligatory JsBin