as you can see from the snippet below,
withLatestFrom never completes if a promise is passed over.
const { combineLatest, range } = rxjs;
const { withLatestFrom } = rxjs.operators;
const a$ = range(1, 5);
const b$ = Promise.resolve('never');
a$.pipe(
withLatestFrom(b$),
).subscribe(console.log);
<script src="https://cdnjs.cloudflare.com/ajax/libs/rxjs/6.5.2/rxjs.umd.js" integrity="sha256-mNXCdYv896VtdKYTBWgurbyH+p9uDUgWE4sYjRnB5dM=" crossorigin="anonymous"></script>
it works just fine if const b$ = of(1);
The documentation is not clear about this behaviour,
any hint?
Per fridoo, I had an incorrect understanding of how withLatestFrom was working. withLatestFrom will not wait for its source to emit. Delaying the source observable or waiting for the async operation by other means (combineLatest) such will cause the observable to emit properly.
There is a way and this should be in my humble opinion a operator, maybe even replace the withLatestFrom.
this.selectedItemsFiltered$.pipe(
switchMap(selectedItemsFiltered => {//TODO: Make out of this a combineLatestFrom operator
return this.isInitialOnSelectedItemsChangeSkipped$.pipe(
take(1),
map(isInitialOnSelectedItemsChangeSkipped => [selectedItemsFiltered, isInitialOnSelectedItemsChangeSkipped] as [string[], boolean])
)
})
...
This code will only trigger if the parent selectedItemsFiltered$ emits, then it will switchMap to the other observable, since this is a switchMap it will await the other Observable, same scenario would have been possible with multiple observables you would just have to combineLatest them inside switchMap.
Related
I'm expecting the following RxJS behavior: for every element in the source array, execute an action (the part commented out) which needs to be awaited to complete, and only then fetch next element in source array, wait again and so on.
But the behavior I get instead, is all elements in the source array are fetched at the same time, then after the delay they are retried all again etc.
import { from, defer, delay, repeat, tap } from 'rxjs';
const source$ = from([1, 2, 3])
const actions$ = source$.pipe(
tap((t) => console.log(t))
// ... action that takes long and needs to be waited for, before going to the next element in source$
)
const timedExecution$ = defer(() => actions$).pipe(
delay(3000),
repeat(3)
)
timedExecution$.subscribe();
I also tried another way, with timer:
import { from, tap, timer } from 'rxjs';
const source$ = from([1, 2, 3])
const actions$ = source$.pipe(
() => timer(0, 3000),
tap((t) => console.log(t))
// actionThatTakesLong() action that takes long and needs to be waited for, before going to the next element in source$
)
actions$.subscribe();
Here, it emits one at a time, but sometimes the actionThatTakesLong() takes longer than the arbitrary 3000 MS value of the timer, and i need it to wait until its done, instead of a hardcoded value of waiting.
Thanks for any hints in advance
Your source Observable is from() which is a synchronous Observable that emits array items one after another immediately on subscription. It doesn't (and can't) care what happens with the values in the chain.
delay() will take each value and delay it by a certain time but it doesn't (and can't) care whether the previous values have reached your observer. It just takes each value and delays it by 3s without waiting for the previous delay to complete so in your case it appears like all values were emitted at the same time.
What you want to do instead is adding concatMap() operator that will wait until the nested delayed Observable completes:
from([1, 2, 3])
.pipe(
concatMap(value => of(value).pipe(delay(3000))),
)
.subscribe(...);
FYI, the second option you are mentioning does something very different than you think:
const actions$ = source$.pipe(
() => timer(0, 3000),
tap(() => ...),
);
This is actually replacing the source Observable from() with a different Observable timer(0, 3000). You're basically using approach used for creating custom operators https://rxjs.dev/guide/operators#creating-new-operators-from-scratch.
I have an array where I need to make single requests using very single data set of this array. The problem that I found difficult to solve was to schedule the calls. Means, only when a request finishes, the next request starts. I was looking for an RxJs queue, but I couldn't simply find a solution.
Example:
function makeRequest(body): Observable<any> {
return someAsyncRequest(body);
}
array.forEach((entry) => {
makeRequest(entry);
});
// This is just an example how the setup is. This code does not work.
// What I need is a queue like feature in RxJs to append requests and wait before the previous one is finished.
You have quite few options, but I suppose concat or forkJoin fits you best. concat calls second API only after previous completes, while forkJoin will do the same, but only if none of them errors. If any of them errors, it will not return anything.
Example with concat:
concat(...array.map(entry => makeRequest(entry)).subscribe()
p.s. import concat as static operator:
import { concat } from 'rxjs'
if you want to have things go out one by one, but still receive all the results at once, i recommend concat -> reduce. looks like this:
concat(...array.map(entry => makeRequest(entry))).pipe(
reduce((completed, curResponse) => completed.concat([curResponse]), [])
).subscribe(allResponses => console.log(allResponses))
this structure achieves the single emission that forkjoin will give you but will do the requests one by one and then gather them once all complete. if you want the results one by one as they complete though, then just concat gets the job done as shown by others
ForkJoin can meet your requirment
‘forkJoin’ waits for each HTTP request to complete and group’s all the observables returned by each HTTP call into a single observable array and finally return that observable array.
public requestDataFromMultipleSources(): Observable<any[]> {
let response1 = this.http.get(requestUrl1);
let response2 = this.http.get(requestUrl2);
let response3 = this.http.get(requestUrl3);
return forkJoin([response1, response2, response3]);
}
subscribe to single observable array and save the responses separately.
this.dataService.requestDataFromMultipleSources().subscribe(responseList => {
this.responseData1 = responseList[0];
this.responseData2 = responseList[1];
this.responseData3 = responseList[2];
});
MDN Promise.all see this you can do it even without observable
Promise.all(array.map(item => makeRequest(item))).then(values =>{
// your logic with recieved data
})
I need to pass three data to one function from three different APIs:
this.service.service1().subscribe( res1 => {
this.service.service1().subscribe( res2 => {
this.service.service1().subscribe( res3 => {
this.funcA(res1, res2, res3);
});
});
});
Is it a good practice to subscribe inside a subscribe?
The correct way is to compose the various observables in some manner then subscribe to the overall flow — how you compose them will depend on your exact requirements.
If you can do them all in parallel:
forkJoin(
this.service.service1(), this.service.service2(), this.service.service3()
).subscribe((res) => {
this.funcA(res[0], res[1], res[2]);
});
If each depends on the result of the previous, you can use mergeMap (formerly known as flatMap) or switchMap:
this.service.service1().pipe(
mergeMap((res1) => this.service.service2(res1)),
mergeMap((res2) => this.service.service3(res2))
).subscribe((res3) => {
// Do something with res3.
});
... and so on. There are many operators to compose observables to cover lots of different scenarios.
Though all of the above help provide solutions to this particular problem none of them seem to address the obvious underlying problem here, specifically:
Is it good way to call subscribe inside subscribe?
tl;dr
No it is not good to call a subscribe inside a subscribe.
Why?
Well because this is not how functional programming is supposed to work. You're not thinking functionally you're thinking procedurally. This isn't necessarily a problem per se, but the whole point of using rxjs (and other reactive programming extensions) is to write functional code.
I'm not going into all the details on what functional programming is but essentially the point of functional programming is to treat data as streams. Streams that are manipulated by functions, and consumed by subscribers. As soon as you add a subscribe inside another subscribe your manipulating data inside a consumer (not inside a stream). So your functional stream is now broken. This prevents other consumers from utilising that stream further down stream in your code. So you've turned your functional stream into a procedure.
Image source, above and more information on pure functional programming here.
You can use forkJoin to combine the Observables into a single value Observable
forkJoin(
this.service.service1(),
this.service.service2(),
this.service.service3()
).pipe(
map(([res1, res2, res3 ]) => {
this.funcA(res1, res2, res3);
})
If the calls can be resolved in parallel you could use forkJoin, like this:
joinedServiceCalls() {
return forkJoin(this.service1(), this.service2(), this.service3());
}
And then subscribe to that method.
https://www.learnrxjs.io/operators/combination/forkjoin.html
Looks strange, I would go this way because it looks cleaner:
async myFunction () {
//...
const res1 = await this.service.service1().toPromise();
const res2 = await this.service.service2().toPromise();
const res3 = await this.service.service3().toPromise();
this.funcA(res1, res2, res3);
//...
}
EDIT
or to do it in parallel
async myFunction () {
//...
let res1;
let res2;
let res3;
[res1,res2,res3] = await Promise.all([this.service.service1().toPromise(),
this.service.service2().toPromise(),
this.service.service3().toPromise()]);
this.funcA(res1, res2, res3);
//...
}
You can use the zip RxJs operator, and then in this case you will only use just one subscribe.
You can then call your function inside that subscribe because all the results are available.
Observable.zip(
this.service.service1(),
this.service.service1(),
this.service.service1()
).subscribe([res1, res2, res3]) {
this.funcA(res1, res2, res3);
}
as mentioned, forkjoin is a good solution, but it emit completed calls only. If these are values that are going to be emitted repeatedly, use I would combineLatest.
I'm trying to teach myself some reactive functional programming. This video from Ben Lesh is showing an example of an observable. My prior reading indicated that an observable is lazy, i.e., it only evaluates after being subscribed to. Strangely enough, this code doesn't require subscription to print to the console.
var Rx = require('rxjs/Rx')
var source = Rx.Observable.from([1,2,3,4,5]);
var newSource = source.filter(x => x % 2 === 1)
.map(x => x + '!')
.forEach(x => console.log(x));
From the RxJS docs:
It seems as though the Observable must be actively resolving the promises emitted by .forEach, I am so confused by this.
Further confusion stems from this code:
var Rx = require('rxjs/Rx')
var source = Rx.Observable.from([1,2,3,4,5]);
var newSource = source.filter(x => x % 2 === 1)
.map(x => x + '!')
.do(x => console.log(x));
Which does not evaluate until running newSource.subscribe();, please help me out to explain the difference behind the two operators here.
Observables are lazy by default. If you perform an operator on an observable, under the hood, rxjs will create a new observable for you that is linked to the previous one. Know that observables are immutable.
However, ForEach is a special kind of operator. It does not return a new Observable but it will subscribe onto the observable under the hood and perform a function on every element emitted by that observable. If you check the source code of the forEach implementation, which is on the Observable class itself you will see the following (just a snippet).
const subscription = this.subscribe((value) => {
if (subscription) {
// if there is a subscription, then we can surmise
// the next handling is asynchronous. Any errors thrown
// need to be rejected explicitly and unsubscribe must be
// called manually
try {
next(value);
} catch (err) {
reject(err);
subscription.unsubscribe();
}
Here we can see the observable is being subscribed to and the value is 'next'-ed. This next function is the function you pass to the forEach call.
I'd like to be able to await on an observable, e.g.
const source = Rx.Observable.create(/* ... */)
//...
await source;
A naive attempt results in the await resolving immediately and not blocking execution
Edit:
The pseudocode for my full intended use case is:
if (condition) {
await observable;
}
// a bunch of other code
I understand that I can move the other code into another separate function and pass it into the subscribe callback, but I'm hoping to be able to avoid that.
You have to pass a promise to await. Convert the observable's next event to a promise and await that.
if (condition) {
await observable.first().toPromise();
}
Edit note: This answer originally used .take(1) but was changed to use .first() which avoids the issue of the Promise never resolving if the stream ends before a value comes through.
As of RxJS v8, toPromise will be removed. Instead, the above can be replaced with await firstValueFrom(observable)
Use the new firstValueFrom() or lastValueFrom() instead of toPromise(), which as pointed out here, is deprecated starting in RxJS 7, and will be removed in RxJS 8.
import { firstValueFrom} from 'rxjs';
import { lastValueFrom } from 'rxjs';
this.myProp = await firstValueFrom(myObservable$);
this.myProp = await lastValueFrom(myObservable$);
This is available in RxJS 7+
See: https://indepth.dev/rxjs-heads-up-topromise-is-being-deprecated/
It likely has to be
await observable.first().toPromise();
As it was noted in comments before, there is substantial difference between take(1) and first() operators when there is empty completed observable.
Observable.empty().first().toPromise() will result in rejection with EmptyError that can be handled accordingly, because there really was no value.
And Observable.empty().take(1).toPromise() will result in resolution with undefined value.
Edit:
.toPromise() is now deprecated in RxJS 7 (source: https://rxjs.dev/deprecations/to-promise)
New answer:
As a replacement to the deprecated toPromise() method, you should use
one of the two built in static conversion functions firstValueFrom or
lastValueFrom.
Example:
import { interval, lastValueFrom } from 'rxjs';
import { take } from 'rxjs/operators';
async function execute() {
const source$ = interval(2000).pipe(take(10));
const finalNumber = await lastValueFrom(source$);
console.log(`The final number is ${finalNumber}`);
}
execute();
// Expected output:
// "The final number is 9"
Old answer:
If toPromise is deprecated for you, you can use .pipe(take(1)).toPromise but as you can see here it's not deprecated.
So please juste use toPromise (RxJs 6) as said:
//return basic observable
const sample = val => Rx.Observable.of(val).delay(5000);
//convert basic observable to promise
const example = sample('First Example')
.toPromise()
//output: 'First Example'
.then(result => {
console.log('From Promise:', result);
});
async/await example:
//return basic observable
const sample = val => Rx.Observable.of(val).delay(5000);
//convert basic observable to promise
const example = await sample('First Example').toPromise()
// output: 'First Example'
console.log('From Promise:', result);
Read more here.
You will need to await a promise, so you will want to use toPromise(). See this for more details on toPromise().
Using toPromise() is not recommended as it is getting depreciated in RxJs 7 onwards. You can use two new operators present in RxJs 7 lastValueFrom() and firstValueFrom(). More details can be found here
const result = await lastValueFrom(myObservable$);
Implementations in Beta version are available here:
firstValueFrom
lastValueFrom
I am using RxJS V 6.4.0, hence I should use deprecated one in V 7.x.x toPromise(). Inspired by other answers, here is what I did with toPromise()
import { first, ... } from 'rxjs/operators';
...
if (condition) {
await observable$.pipe(first()).toPromise();
}
...
Note how I used last() inside a pipe(). Because on mine observable.first() does not works just like mentioned by macil
Hope this helps others who using RxJS V 6.x.x as I do :).
Thanks.