RXJS: How to throttle Observable from Promise - rxjs

I am using RxJS 5.5.10.
I try to throttle an observable to fire every 5 seconds.
This Observable is based on a Promise.
Rx.Observable.fromPromise(mongo.AllWishes)
.flatMap(array => Rx.Observable.from(array))
.pluck('url')
.filter(s => s !== undefined)
.subscribe(m => console.log(m))
I understand that I can use the throttle operator to emit values only after a given time
Rx.Observable.interval(1000)
.throttle(val => Rx.Observable.interval(5000)
.subscribe(m => console.log('ping'))
But when I try something like
Rx.Observable.fromPromise(mongo.AllWishes)
.throttle(val => Rx.Observable.interval(5000))
.flatMap(array => Rx.Observable.from(array))
.pluck('url')
.filter(s => s !== undefined)
.subscribe(m => console.log(m))
I get an error
rxjs/observable/PromiseObservable.js:76
root_1.root.setTimeout(function () { throw err; });
^
TypeError: this.durationSelector is not a function
What am I missing ?
Thank your for your help

I'm not entirely clear on your expectations. It looks like you are getting an array from a promise and are then wanting to emit each value sequentially with 5 seconds in-between each item.
If so, I think that this should do what you want. As far as your error it is hard to tell without being able to run your code. I'm assuming it has something to do with your promise since I can replace mongo.AllWishes with my own promise and it doesn't error.
const data = [
{ url: 'https://something.com/1' },
{ url: 'https://something.com/2' },
{ url: 'https://something.com/3' },
{ url: 'https://something.com/4' },
{ url: 'https://something.com/5' }
];
const myPromise = new Promise((resolve) => {
setTimeout(() => { resolve(data); }, 1000);
});
Rx.Observable.fromPromise(myPromise)
.flatMap(x => {
return Rx.Observable.timer(0, 1000)
.takeWhile(i => i < x.length)
.map(i => x[i]);
})
.pluck('url')
.subscribe((url) => { console.log(url); });
<script src="https://cdnjs.cloudflare.com/ajax/libs/rxjs/5.5.10/Rx.min.js"></script>

Related

rxjs ForkJoin but have it complete when 1 of 2 observables give a value

I'm new to rsjx but I'm looking for a solution like the Forkjoin but it should complete when 1 of the 2 observables have a value. It needs to be like a ForkJoin because I need to know which observable got a value.
example:
I'm loading categories and I have an autocomplete. Categories is an observable and the valueChanges is returned as an observable.
#Select(IngredientCategoryState.selectIngredientCategories) ingredientCategories$!: Observable<IngredientCategory[]>;
this.filter = this.ingredientForm.controls['category'].valueChanges.pipe(
map((data) => {
if (typeof data === 'object') {
return data.name;
}
return data;
})
)
Then I used the forkJoin but then it requires both observables to have a value
this.filteredIngredientCategories$ = forkJoin({
ingredientCategories: this.ingredientCategories$,
filter: this.filter
}).pipe(
map(({ ingredientCategories, filter }) => {
return this._filter(ingredientCategories, filter);
})
);
UPDATE
I solved it with a combineLatest and triggering the autocomplete field
combineLatest([this.ingredientCategories$, this.filter$])
.pipe(
map((data) => {
return this._filter(data[0], data[1]);
})
).subscribe((ingredientCategories) => {
this.filteredIngredientCategories = ingredientCategories;
});
this.ingredientForm.controls['category'].setValue('');
You can use race to use the first source to emit.
I need to know which observable got a value
Instead of "knowing" which one emitted, you can transform the output of each source to return the object shape you need:
this.filteredIngredientCategories$ = race(
this.ingredientCategories$.pipe(
map(ingredientCategories => ({ ingredientCategories, filter: undefined }))
),
this.filter.pipe(
map(filter => ({ filter, ingredientCategories: undefined }))
)
).pipe(
map(({ ingredientCategories, filter }) => {
return this._filter(ingredientCategories, filter);
})
);
Here's a working StackBlitz demo.

Retry operator working with observable but not with subject

I try to use rxjs rety operator, its work fine with observable:
const obs$: Observable<number> = new Observable((observer) => {
observer.next(1);
observer.complete();
});
obs$
.pipe(
mergeMap(async () => [
await dataService.getAllSomthing(),
await dataService.getAllomthingElse(),
await dataService.getAllSomthingElseElse(),
]),
map(([somthing, somthingElse, somthingElseElse]) => {
dispatch(
enableToasterAction({
text: "good",
type: ToasterType.Success,
})
);
}),
retry(2),
catchError((err) => {
return of(null);
})
)
.subscribe((val: any) => {});
But its not work when I using Subject:
const sub=new Subject();
sub
.pipe(
switchMap(async ({ somthinng}: any) => {
return [await dataService.getSomthing(somthinng)];
}),
map(([somthinngRes]) => {
dispatch(
enableToasterAction({
text: "good",
type: ToasterType.Success,
})
);
}),
retry(2),
catchError((err) => {
return of(null);
})
)
.subscribe((val: any) => {});
sub.next({});
Someone can help me to understand what the difference between them, why its work with observable, but not with subject ?
You can retry an cold observable but not hot observable (subject)
if you want to retry a action trigger by hot observable, you can however move the retry() operator to inner observable. For example
fromEvent(document,'click').pipe(
switchMap(evt=>from(fetch('someurl')).pipe(retry(2))
)
That way the http call triggered by click will retry 2 times when it fails
Subject has an internal state and once it receives complete or error notification it marks itself as isStopped and will never ever emit anything.
So retry() tries to resubscribe but the source Subject will just return Subscription.EMPTY and won't make a real subscribtion.

how to use multicasting obs with behavioursubject?

In general we need behavior subject functionality. But only on first subscription we should send subscribe to server in REST. And to send unsubscribe on the last unsubscribe, and all late observers subscribed will gwt the latest json recwived from the first. can i do it using rxjs operaTors and how? or shoul i use custom obserbale ?
currently the custom code for this is this:
public observable: Observable<TPattern> = new Observable((observer: Observer<TPattern>) => {
this._observers.push(observer);
if (this._observers.length === 1) {
this._subscription = this.httpRequestStream$
.pipe(
map((jsonObj: any) => {
this._pattern = jsonObj.Data;
return this._pattern;
})
)
.subscribe(
(data) => this._observers.forEach((obs) => obs.next(data)),
(error) => this._observers.forEach((obs) => obs.error(error)),
() => this._observers.forEach((obs) => obs.complete())
);
}
if (this._pattern !== null) {
observer.next(this._pattern); // send last updated array
}
return () => {
const index: number = this._observers.findIndex((element) => element === observer);
this._observers.splice(index, 1);
if (this._observers.length === 0) {
this._subscription.unsubscribe();
this._pattern = null; // clear pattern when unsubscribed
}
};
});
Sounds like you need a shareReplay(1), it will share the latest response with all subscribes.
const stream$ = httpRequestStream$.pipe(
shareReplay(1),
),
stream$.subscribe(); // sends the request and gets its result
stream$.subscribe(); // doesn't send it but gets cached result
stream$.subscribe(); // doesn't send it but gets cached result
stream$.subscribe(); // doesn't send it but gets cached result

Intercept each request with before and after hooks

I have to make 5 requests (order doesn't matter) to 5 different endpoints. The URL of these endpoints is the same, except for the business line. These business lines are the array of the from.
I want show a skeleton loader before each request and hide once it finish. So, basically the flow is:
1. [Hook - before request]
2. [Log of data fetched]
3. [Hook - after request]
This is my service:
export function getInsurances(
userIdentity: string,
hooks?: RequestHooks
): Observable<Policy[]> {
return from(["all", "vehicle", "health", "soat", "plans"]).pipe(
tap(() => hooks?.beforeRequest && hooks.beforeRequest()),
flatMap<string, Observable<Policy[]>>(businessLine => {
return InsurancesApi.getPolicies<Policy>(
userIdentity,
businessLine
).pipe(
map(policies => {
return policies.map(policy => PolicyStandarizer(policy));
}),
finalize(() => {
hooks?.afterRequest && hooks.afterRequest();
})
);
}),
catchError(err => of(err)),
takeUntil(HttpCancelator)
);
}
This is my subscribe:
const hooks = {
beforeRequest() {
Log.info("Before Request");
setStatus(HttpRequestStatus.PENDING);
},
afterRequest() {
Log.warn("After Request");
setStatus(HttpRequestStatus.RESOLVED);
},
};
getInsurances(userIdentity, hooks).subscribe(
policies => {
Log.normal("Policies:", policies);
setInsurances(policies);
},
(err: Error) => {
setError(err);
}
);
And have this output (sorry for paste the link, I can't embed the image because rep):
https://i.stack.imgur.com/Nbq49.png
The finalize is working fine, but the tap is executing five times at once.
Thank you.
I think you get this behavior because from emits the items synchronously, so its essentially the same as doing:
for (let i = 0; i < arr.length; i++) {
console.log('before req');
observer.next(arr[i]);
}
observer.complete();
afterRequest is shown properly because the actions involved are asynchronous.
If you want to trigger that event only once, before all the requests are fired, you could try this:
from([...])
.pipe(
finalize(() => hooks?.beforeRequest && hooks.beforeRequest()),
flatMap(/* ... */)
)
EDIT - log event before each request
flatMap(
value => concat(
of(null).pipe(
tap(() => hooks?.beforeRequest && hooks.beforeRequest()),
ignoreElements(), // Not interested in this observable's values
),
InsurancesApi.getPolicies(/* ... */)
)
)

In RXJS, is there a way to omit the res and err calls on .subscribe()?

Given an observable where you subscribe:
Observable.subscribe(
(res) => {},
(err) => {},
() => {
// do some thing on complete
}
);
Is it a requirement to write (res) and (err)?
Reason being, I'm using Observable.forkJoin([Observable1, Observable2]).subscribe, and I only care about when they are done, and maybe if they yield an error.
To omit the next and error functions, pass undefined for those parameters:
Rx.Observable
.from([0, 1, 2])
.subscribe(
undefined,
undefined,
() => console.log("completed")
);
I would opt to use the .finally() operator instead of omitting the res and err callbacks. This to better convey what it is that you want to happen.
Rx.Observable.forkJoin([obs1, obs2])
.finally(() => console.log('done'))
.subscribe();
Note that it is not required to pass the callbacks to subscribe().
If we are talking of RxJS5 at least, you can also define the specific subscriber methods you want.
Observable.subscribe(
(res) => {},
(err) => {},
() => {
// do some thing on complete
}
);
becomes
Observable.subscribe(
complete: () => {
// do some thing on complete
}
);
As a side note, the other methods that can be explicitly defined are next and error.

Resources