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

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

Related

Delay batch of observables with RxJS

I perform http requests to my db and have noticed that if I send all the requests at once, some of them will get a timeout errors. I'd like to add a delay between calls so the server doesn't get overloaded. I'm trying to find the RxJS solution to this problem and don't want to add a setTimeout.
Here is what I currently do:
let observables = [];
for(let int = 0; int < 10000; int++){
observables.push(new Observable((observer) => {
db.add(doc[int], (err, result)=>{
observer.next();
observer.complete();
})
}))
}
forkJoin(observables).subscribe(
data => {
},
error => {
console.log(error);
},
() => {
db.close();
}
);
You can indeed achieve this with Rxjs quite nicely. You'll need higher order observables, which means you'll emit an observable into an observable, and the higher order observable will flatten this out for you.
The nice thing about this approach is that you can easily run X requests in // without having to manage the pool of requests yourself.
Here's the working code:
import { Observable, Subject } from "rxjs";
import { mergeAll, take, tap } from "rxjs/operators";
// this is just a mock to demonstrate how it'd behave if the API was
// taking 2s to reply for a call
const mockDbAddHtppCall = (id, cb) =>
setTimeout(() => {
cb(null, `some result for call "${id}"`);
}, 2000);
// I have no idea what your response type looks like so I'm assigning
// any but of course you should have your own type instead of this
type YourRequestType = any;
const NUMBER_OF_ITEMS_TO_FETCH = 10;
const calls$$ = new Subject<Observable<YourRequestType>>();
calls$$
.pipe(
mergeAll(3),
take(NUMBER_OF_ITEMS_TO_FETCH),
tap({ complete: () => console.log(`All calls are done`) })
)
.subscribe(console.log);
for (let id = 0; id < NUMBER_OF_ITEMS_TO_FETCH; id++) {
calls$$.next(
new Observable(observer => {
console.log(`Starting a request for ID "${id}""`);
mockDbAddHtppCall(id, (err, result) => {
if (err) {
observer.error(err);
} else {
observer.next(result);
observer.complete();
}
});
})
);
}
And a live demo on Stackblitz: https://stackblitz.com/edit/rxjs-z1x5m9
Please open the console of your browser and note that the console log showing when a call is being triggered starts straight away for 3 of them, and then wait for 1 to finish before picking up another one.
Looks like you could use an initial timer to trigger the http calls. e.g.
timer(delayTime).pipe(combineLatest(()=>sendHttpRequest()));
This would only trigger the sendHttpRequest() method after the timer observable had completed.
So with your solution. You could do the following...
observables.push(
timer(delay + int).pipe(combineLatest(new Observable((observer) => {
db.add(doc[int], (err, result)=>{
observer.next();
observer.complete();
}))
}))
Where delay could probably start off at 0 and you could increase it using the int index of your loop by some margin.
Timer docs: https://www.learnrxjs.io/learn-rxjs/operators/creation/timer
Combine latest docs: https://www.learnrxjs.io/learn-rxjs/operators/combination/combinelatest
merge with concurrent value:
mergeAll and mergeMap both allow you to define the max number of subscribed observables. mergeAll(1)/mergeMap(LAMBDA, 1) is basically concatAll()/concatMap(LAMBDA).
merge is basically just the static mergeAll
Here's how you might use that:
let observables = [...Array(10000).keys()].map(intV =>
new Observable(observer => {
db.add(doc[intV], (err, result) => {
observer.next();
observer.complete();
});
})
);
const MAX_CONCURRENT_REQUESTS = 10;
merge(...observables, MAX_CONCURRENT_REQUESTS).subscribe({
next: data => {},
error: err => console.log(err),
complete: () => db.close()
});
Of note: This doesn't batch your calls, but it should solve the problem described and it may be a bit faster than batching as well.
mergeMap with concurrent value:
Perhaps a slightly more RxJS way using range and mergeMap
const MAX_CONCURRENT_REQUESTS = 10;
range(0, 10000).pipe(
mergeMap(intV =>
new Observable(observer => {
db.add(doc[intV], (err, result) => {
observer.next();
observer.complete();
});
}),
MAX_CONCURRENT_REQUESTS
)
).subscribe({
next: data => {},
error: err => console.log(err),
complete: () => db.close()
});

Why does this little rxjs-code-snippet using the concat-operator throw an error?

Preconditions:
The ref.getDownload() returns an Observable which only can be subscribed, if the
task.snapshotChanges()-Observable completed.
This code-snippet works:
task.snapshotChanges().subscribe({
complete: () => {
ref.getDownloadURL().subscribe((downloadUrl) => console.log(downloadUrl));
}
});
This code-snippet does NOT work:
concat(
task.snapshotChanges(),
ref.getDownloadURL()
).pipe(
last()
).subscribe((downloadUrl) => console.log(downloadUrl));
getDownloadUrl throws an error (404 file not found), because it seems
ref.getDownloadUrl is subscribed to early.
Why subscribes the ref.getDownloaded()-Observable and does not wait until task.snapshotChanges() completes? The concat-operator should ensure this behaviour.
Or am I wrong?
The function ref.getDownloadURL() is called when the concat(..) Observable is created. See:
const { of, concat } = rxjs;
const { delay } = rxjs.operators;
const fetch1 = () => { console.log('run fetch1'); return of('from 1').pipe(delay(2000)) }
const fetch2 = () => { console.log('run fetch2'); return of('from 2').pipe(delay(2000)) }
concat(fetch1(), fetch2()).subscribe(console.log);
<script src="https://unpkg.com/rxjs/bundles/rxjs.umd.min.js"></script>
ref.getDownloadURL() seems to query the database directly when it gets called and not when the Observable it returns gets subscribed to.
You can wrap ref.getDownloadURL() with defer to only execute it when the Observable is subscribed to.
const { of, concat, defer } = rxjs;
const { delay } = rxjs.operators;
const fetch1 = () => { console.log('run fetch1'); return of('from 1').pipe(delay(2000)) }
const fetch2 = () => { console.log('run fetch2'); return of('from 2').pipe(delay(2000)) }
concat(fetch1(), defer(() => fetch2())).subscribe(console.log);
<script src="https://unpkg.com/rxjs/bundles/rxjs.umd.min.js"></script>
Also see my answer here https://stackoverflow.com/a/57671521/9423231

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.

InfiniteLoader and react-redux

Component InfiniteLoader from react-virtualised requires function passed as property loadMoreRows to have signature like { startIndex: number, stopIndex: number }): Promise.
I'm using redux in my project, so loadMoreRows is a redux action creator like this:
const fetchEntities(start, stop) {
return fetch(`${myUrl}&start=${start}?stop=${stop}`)
}
const loadMoreRows = ({ startIndex, stopIndex }) => {
return (dispatch, getState) => {
return function(dispatch) {
return fetchEntities(startIndex, stopIndex).then(
items => dispatch(simpleAction(items)),
error => console.log(error)
)
}
}
}
after that, this action is connected to react component containing InfiniteLoader using connect function from react-redux.
So I'm not sure, how can I satisfy signature requirement, as redux action creators don't return any value/
eyeinthebrick is correct. A Promise is not a required return value.
When you "connect" a Redux action-creator, invoking it (dispatching it) actually returns a Promise. So for example I think you could do something more like this...
function fetchEntities (start, stop) {
return fetch(`${myUrl}&start=${start}?stop=${stop}`)
}
const loadMoreRows = ({ startIndex, stopIndex }) => {
return async (dispatch, getState) => {
try {
const items = await fetchEntities(startIndex, stopIndex)
await dispatch(simpleAction(items))
} catch (error) {
console.log(error)
}
}
}
At which point InfiniteLoader can just await the returned Redux promise.

Create an Rx.Subject using Subject.create that allows onNext without subscription

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

Resources