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.
Related
I've been looked on StackOverflow and haven't seen any direct examples of what I'm asking. I'm reading this article on memoization link here if you want to look.
It appears to me that you should be able to run them all together and use the return value from getSoupRecipe() as input for hireSoupChef()
async function makeSoupFromType(soupType) {
let [ soupRecipe, soupPan, soupChef ] = await Promise.all([
getSoupRecipe(soupType),
buySoupPan(),
hireSoupChef(soupRecipe.requiredSkills)
]);
return await makeSoup(soupChef, soupRecipe, soupPan);
}
So the question is can all three async functions run at the same time and once getSoupRecipe returns I use it's variable name (which is soupRecipe) as input for hireSoupChef.
I would post all the other code here for you to see but I think it would probably make the question look too daunting, so the link is above. You don't necessarily have to look at it to understand I don't think because I think I've stated the question right, but nonetheless if you want to you can.
Not by itself, no. In your example the soupRecipe (and the other two variables) are only initialised after the Promise.all(…) has been awaited, so it can't be used in the expressions that are passed to Promise.all as an argument. There's no magic going on here, Promise.all is not part of the syntax but really just returning one promise that fulfills with an array once.
However, the approach outlined in this answer to How do I access previous promise results in a .then() chain? does enable the desired chaining of asynchronous actions:
async function makeSoupFromType(soupType) {
const soupRecipePromise = getSoupRecipe(soupType);
const [soupRecipe, soupPan, soupChef] = await Promise.all([
soupRecipePromise,
buySoupPan(),
soupRecipePromise.then(({requiredSkills}) => hireSoupChef(requiredSkills))
]);
return makeSoup(soupChef, soupRecipe, soupPan);
}
Alternatively, with plain async/await you could also use an IIFE to avoid the then() call:
async function makeSoupFromType(soupType) {
const [soupPan, [soupRecipe, soupChef]] = await Promise.all([
buySoupPan(),
(async () => {
const soupRecipe = await getSoupRecipe(soupType);
const soupChef = await hireSoupChef(soupRecipe.requiredSkills);
return [soupRecipe, soupChef];
})(),
]);
return makeSoup(soupChef, soupRecipe, soupPan);
}
Perhaps this is a limitation of the language, but I am trying to figure out how I could get away with using a single await keyword for a set of sequential promise resolutions. I am trying to achieve something readable, like the following:
const workoutExercises = await Workout.get(1).workoutExercises;
Workout.get accesses a database, and so returns a Promise. The promise resolves to an instance of Workout, which has a getter method on the instance called workoutExercises, which is also a Promise that resolves to an array of WorkoutExercises.
The above code does not work, and only waits for Workout.get to resolve; doesn't also wait for .workoutExercises to resolve. The following below code examples DO work, but I am trying to achieve a one-liner / more readability:
1:
const workoutExercises = await (await Workout.get(1)).workoutExercises;
2:
const workout = await Workout.get(1);
const workoutExercises = await workout.workoutExercises;
Edit #1
Updated the title and description to clarify that the problem doesn't revolve around the resolution of a Promise chain, but the resolution of a Promise based on the resolution of a preceding Promise.
Workout.get --> <Promise> --> workout --> .workoutExercises --> <Promise> -> desired result
Use .then() on the results of .get(1), and extract workoutExercises. The one liner is very readable.
Example:
(async() => {
const Workout = {
get: () => Promise.resolve({
workoutExercises: Promise.resolve(3)
})
};
const workoutExercises = await Workout.get(1).then(w => w.workoutExercises);
console.log(workoutExercises)
})()
Is it possible to use one await keyword for resolving a set of sequential promises
Because these promises are not actually chained, it is not. You have a promise that resolves to an object that has a property that when you access it has a getter that creates a new promise that resolves to the value you finally want. That's two completely separate promises and you'll have to use await or .then() on each of the two so you can't do this with one single await.
You can use a little intervening code to chain them together and even put them into a helper function:
function getWorkoutExercises(n) {
return Workout.get(n).then(workout => {
// chain this promise to the previous one
return workout.workoutExercises;
});
}
// usage
getWorkoutExercises(1).then(exercises => {
console.log(exercises);
}).catch(err => {
console.log(err);
});
or
// usage
try {
let exercises = await getWorkoutExercises(1);
console.log(exercises);
} catch(e) {
console.log(e);
}
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.
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.
How can I use Async/Await on HttpService using NestJs?
The below code doesn`t works:
async create(data) {
return await this.httpService.post(url, data);
}
The HttpModule uses Observable not Promise which doesn't work with async/await. All HttpService methods return Observable<AxiosResponse<T>>.
So you can either transform it to a Promise and then use await when calling it or just return the Observable and let the caller handle it.
create(data): Promise<AxiosResponse> {
return this.httpService.post(url, data).toPromise();
^^^^^^^^^^^^^
}
Note that return await is almost (with the exception of try catch) always redundant.
Update 2022
toPromise is deprecated. Instead, you can use firstValueFrom:
import { firstValueFrom } from 'rxjs';
// ...
return firstValueFrom(this.httpService.post(url, data))
As toPromise() is being deprecated, you can replace it with firstValueFrom or lastValueFrom
For example:
const resp = await firstValueFrom(this.http.post(`http://localhost:3000/myApi`)
https://rxjs.dev/deprecations/to-promise
rxjs library is most powerful concurrency package that chosen form handling system event like click, external request like get data or delete record and ....
The main concept behind this library is:
handle data that receive in future
therefor you most use 3 argument in observable object like
observablSource.subscribe(
data => { ... },
failure => { ... },
compelete => { ... }
)
but for most backend developer use Promises that comes from ECMAScript 6 feature and is native part of JavaScript.
By default in Angular 4+ and Nest.js use rxjs that support Observable. In technical details you can find a solution for change automatic observable to promise.
const data: Observable<any>;
data.from([
{
id: 1,
name: 'mahdi'
},
{
id: 2,
name: 'reza'
},
])
now you have simulate a request with observable type from server. if you want to convert it to Pormise use chained method like example:
data.toPromise();
from this step you have promised object and form using it better to attach async/await
async userList( URL: string | URLPattern ) {
const userList = await this.http.get<any>( URL ).toPromise();
...
}
try these below one instead of only async-await.
There are some basic reasons behind the deprecation of toPromise.
We know that Promise and Observables that both collections may produce values over time.
where Observables return none or more and Promise return only one value when resolve successfully.
there is a main reason behind the seen
Official: https://rxjs.dev/deprecations/to-promise
Stack:Rxjs toPromise() deprecated
Now we have 2 new functions.
lastValueFrom : last value that has arrived when the Observable completes for more https://rxjs.dev/api/index/function/firstValueFrom
firstValueFrom: you might want to take the first value as it arrives without waiting an Observable to complete for more https://rxjs.dev/api/index/function/lastValueFrom
Following is complete example for working code:
.toPromise() is actually missing
async getAuthToken() {
const payload = {
"SCOPE": this.configService.get<string>('SCOPE'),
"EMAIL_ID": this.configService.get<string>('EMAIL_ID'),
"PASSWORD": this.configService.get<string>('PASSWORD'),
};
const url = this.configService.get<string>('AUTHTOKEN_URL')
const response = await this.httpService.post(
url,
payload
).toPromise();
console.log(response.data);
return response.data;
}
You can just add the .toPromise() at the end of each method call but then you lose the power of observables, like it’s ability to add retry to failed http call by just adding the retry operator.
You can implement this abilities by yourself and create your own module or just use package that already implemented it like this : https://www.npmjs.com/package/nestjs-http-promise