How to use async await with subscribe angular - javascript

I'm calling createOtherKind() function and trying to use the value of this.listKinds. My problem is that isKindOtherCreated is not waiting this.getKinds() to finish and this.listKinds is undefined.
How can i do it?
Code:
getKinds(): void {
this.detailsService.getKinds().subscribe(async response =>{
this.listKinds = await response.body;
})
}
async createOtherKind() {
await this.getKinds();
const isKindOtherCreated = this.listKinds.find(kind => kind.name === "Other");
if(!isKindOtherCreated) {
this.createdKind.name = "Other";
this.createKind();
}
}

You can call the createOtherKind() method inside getKinds() method. This is how you will get the value from this.listKinds variable. subscribe is async method. So, it does not wait for the reponse.
Here is Example:
getKinds(): void {
this.detailsService.getKinds().subscribe(async response =>{
this.listKinds = response.body;
await this.createOtherKind();
})
}
async createOtherKind() {
const isKindOtherCreated = this.listKinds.find(kind => kind.name === "Other");
if(!isKindOtherCreated) {
this.createdKind.name = "Other";
this.createKind();
}
}

The issue is happening because you're mixing Observables (where you have subscribe) with async/await. If you want to stick to using promises you could revise your getKinds function to something like this:
getKinds(): void {
// Convert your observable to a Promise so you can use `await` on it
return this.detailsService.getKinds().toPromise().then((response) => {
this.listKinds = await response.body;
});
}
For more details, I recommend taking a look at this similar question.

You use subscribe here, so I guess this.detailsService.getKinds() returns an observable. You can make things with it then return it to be able to subscribe something else in another part of your code. like:
getKinds(): Observable<any> {
let myObs = this.detailsService.getKinds(); //Getting your observable
//Reading result
myObs.subscribe(response => {
this.listKinds = response.body;
});
//Returning it to use it somewhere else
return myObs;
}
createOtherKind() {
//Make request and subscribe to observable
this.getKinds().subscribe( res => {
const isKindOtherCreated = this.listKinds.find(kind => kind.name === "Other");
if(!isKindOtherCreated) {
this.createdKind.name = "Other";
this.createKind();
}
}
}
EDIT
As said by Andres2142, subscribe twice isn't exactly the right way to act. It should work but the result isn't perfectly stable, the best way is to replace the first subscribe by a tap pipe since it is desinged to perform side-effects. So a better answer is:
getKinds(): Observable<any> {
return this.detailsService.getKinds()
.pipe(
tap( (response) => {
this.listKinds = response.body;
})
);
}
// createOtherKind() Doesn't need to change

Related

How to wait for API response in the nested subscription before executing next line of code

I have a method that should return me a boolean value, the problem is that this is asynchronus and I want to avoid race condition..
So I have some method for example:
someMethod(data: any): boolean{
let isOccupied = false;
firstValueFrom(this.checkIfOccupied('102')).then(toilet=> isOccupied = toilet.name === data.name);
this.someObject.isOccupied = isOccupied;
return isOccupied;
}
So before I proceed to the line with this.someObject... I want to wait for things that are happening inside of then( )
And the checkIfOccupied looks like this:
checkIfOccupied(toiletName: string): Observable<Toilet> {
return this.store$
.select(selectAlarmsForToilet(toiletName))
.pipe(
filter(res => !!res),
take(1),
switchMap((alarms: AlarmsObject[]) => {
alarms.forEach(alarm => {
if (Object.keys(alarm)[0].includes('occupied')) {
const toiletId = this.getToiletIdFromAlarm(toiletName, alarm); <= this method only finds needed ID
if (toiletId ) {
return this.toiletService.getToiletForId(toiletId ); <= this is API call
}
}
});
return of({} as SomeObject);
}));
}
I have tried to make it async and then use await in the someMethod but it doesn't work. Probably I made some mistake in the code (I dont want to make someMethod async - is it even possible?)
You are very close in your thinking.
Consider the words you stated, which I will paraphase:
"Before I proceed to the next line, I want to wait for something"
So change your code like so:
async someMethod(data: any): Promise<boolean>{
let isOccupied = false;
data = await firstValueFrom(this.checkIfOccupied('102'));
this.someObject.isOccupied = toilet.name === data.name;
return toilet.name === data.name;
}
You are using the Arrays.prototype.forEach inside your switchMap, which does not know anything about asynchronousity.
To keep up with race conditions, you can use the forkJoin Operator, which waits for Observables in arrays to complete such as:
someMethod(data: any): boolean {
let isOccupied = false;
firstValueFrom(this.checkIfOccupied('102')).then(toilet => isOccupied =
toilet.name === data.name);
this.someObject.isOccupied = isOccupied;
return isOccupied;
}
checkIfOccupied(toiletName: string): Observable<any> {
return this.store$
.select(selectAlarmsForToilet(toiletName))
.pipe(
filter(res => !!res),
take(1),
switchMap((alarms: AlarmsObject[]) => {
return alarms.length ? forkJoin(alarms
.filter(alarm => Object.keys(alarm)[0].includes('occupied'))
.map((alarm: any) => this.getToiletIdFromAlarm(toiletName, alarm))
.filter(toiletId => !!toiledId)
.map((toiletId: any) => {
return this.toiletService.getToiletForId(toiletId); // api call
})) : of([]);
}));
}

Angular make an api call async and use the result

I'm trying to make an API call
ngOnInit(){
this.function1()
}
function1(){
this.userService.getUser()
.then((data) => {
this.user = data.user
this.userM = data.userM
// here problem: If I make a console.log(this.userM) it result at the beginning empty
if(this.userM.length > 0 ) {
console.log("goInside")}
else {
console.log("doesn't enter")
//it enters always there
}
}
}
in my service:
async getUser(): Promise<any>{
let user: any = []
let userM: any = []
//.... operation to populate
return {user, userM}
}
When I make an api call I would to use the this.userM array to make some operations. But it results always empty at the beginning, but it doens't enter anymore in if. How can I do?
Make use of rxjs, try using an Observable instead:
import { of } from 'rxjs';
getUsers(): Observable<any> {
let user: any = []
let userM: any = []
//.... operation to populate
return of({user, userM});
}
And then, within your component:
ngOnInit(){
this.function1()
}
function1(){
this.userService.getUser().subscribe((response) => {
const { user, userM } = response;
if (userM.length) console.log('There is data in it!')
else console.log('No data found...');
});
}
I suggest not using Promises in an Angular project unless necessary, you have at your disposal rxjs along with tons of useful operators for handling async calls/streams of data

trigger a synchronous call inside loop wait till two api calls get success then next iteration need to starts in angular 6

Tried with below code not wait for post call success jumping to next iteration before response comes.
Requirement:Need to have next iteration after the success of two api(POST/PATCH) calls
for (item of data) {
A(item)
}
A(value) {
const resp = this.post(url, {
'rationale': value['rationale']
})
.mergeMap(tempObj => {
value['detail'] = tempObj['id']
return this.patch(url, value['extid'], value)
})
.subscribe()
}
Recently I have used the toPromise function with angular http to turn an observable into a promise. If you have the outer loop inside an async function, this may work:
// This must be executed inside an async function
for (item of data) {
await A(item)
}
async A(value) {
const resp = await this.post(url, {
'rationale': value['rationale']
})
.mergeMap(tempObj => {
value['detail'] = tempObj['id']
return this.patch(url, value['extid'], value)
}).toPromise();
}
Use from to emit the items of an array. Use concatMap to map to an Observable and only map to the next one when the previous completed.
const resp$ = from(data).pipe(
concatMap(value => this.post(url, { 'rationale': value['rationale'] }).pipe(
switchMap(tempObj => {
value['detail'] = tempObj['id']
return this.patch(url, value['extid'], value)
})
))
)
I used switchMap instead of mergeMap to indicate that the feature of mergeMap to run multiple Observables simultaneously isn't used.
You could use:
<form (ngSubmit)="submit$.next(form.value)" ...
In your component:
submit$= new Subject();
ngOnInit {
submit$.pipe(
exhaustMap(value =>
this.post(url, {'rationale': value.rationale}))
.pipe(concatMap( response => {
value.detail = response.id;
return this.patch(url, value.extid, value);
}))).subscribe(); // rember to handle unsubcribe
}
The reason I use exhaustMap generally post and path are mutating calls, so that operator ensures first submit is process ignore the rest while processing AKA avoid double submit
An even better way is using ngrx effects, which if you dont know it yet I recomend to learn it
submit$ = createEffect(
() => this.actions$.pipe(
ofType(FeatureActions.submit),
exhaustMap( ({value}) => // if action has value property
this.post(url, { rationale : value.rationale}))
.pipe(concatMap( response => {
value.detail = response.id;
return this.patch(url, value.extid, value);
})),
map(result => FeatureActions.submitSuccess(result))
)
);

Assign value returned by Observable.subscribe() to a const

I'm trying to send a request to my Django API from an Ionic4-Angular front-end, the problem is that I've not become familiar with this tecnology or even javascript yet.
In the following code I'm trying to return the subscribe data and assign it to a const.
async getUserLogged() {
const tkn = await this.authService.getToken();
const user = await this.http
.post('http://localhost:8000/api/getuser/', {
token: tkn
})
.subscribe(response => {
console.log(response);
return response;
});
console.log(user);
return user;
}
The first console.log contains the real object i'm searching for.
However, the second one prints a Subscriber object.
Can anyone explain me this behaviour and how can I fix it
Here is how I would set up this method:
getUserLogged() {
return this.authService.getToken().pipe(
switchMap(tkn=> {
return this.http.post('http://localhost:8000/api/getuser/', {
token: tkn
})
})
);
}
You then use this method as follows:
getUserLogged().subscribe(userData => console.log(userData));
This approach uses the switchMap operator that only calls http.post once authService.getToken returns the token.You can find the documentation to all RxJs operators here.
callback function of subscribe cannot return any value to user. it only gets Subscriber object as you mentioned which is result of observable.
If you want to use async, await approach, you need to use toPromise method to make an observable a promise.
async getUserLogged() {
const tkn = await this.authService.getToken();
const user = await this.http
.post('http://localhost:8000/api/getuser/', {
token: tkn
}).toPromise();
console.log(user);
return user;
}
Converting your observable response to a promise, makes it easy to call from where you need.
const getUserLogged = () => {
const tkn = await this.authService.getToken();
return this.http
.post('http://localhost:8000/api/getuser/', {
token: tkn
})
.toPromise();
};
const someOtherFunc = async () => {
const user = await getUserLogged();
console.log({ user });
};

Typescript: how to structure a fetch API call inside a method that returns a Promise response

Maybe a trivial one, but I am new with Typescript and fetch API.
In an exported class I have a public method remoteFetchSomething like:
export class className {
remoteFetchSomething = (url : string) : Promise<Response> => {
return fetch(url)
.then(
(r) => r.json()
)
.catch((e) => {
console.log("API errore fetching " + objectType);
});
}
}
export const classInstance = new className();
The method queries a remote JSON API service, and in the code, I am using it like:
import { classInstance } from ...
classInstance.remoteFetchSomething('https://example.url')
.then((json) => {
console.log(json);
}
)
The console.log is actually showing the results, but the remoteFetchSomething returns a Promise and I am unable to parse and access the JSON object values.
I would like to wait for the response before executing the remaining code, but how do I unwrap content from promise? Should I again put another .then? What am I missing?
Thank you.
By now I resolved the problem defining the return type of the remoteFetch as any:
remoteFetchSomething = (url : string) : any => {
return fetch(url)
.then(
(r) => r.json()
)
.catch((e) => {
console.log("API errore fetching " + objectType);
});
}
And now I can access JSON values like data below:
classInstance.remoteFetchSomething('https://example.url').then(
(json) => {
console.dump(json.data);
}
)
[sincerely still not clear why I cant' use the Promise<Response> type]
You can't synchronously block while waiting for a request in javascript, it would lock up the user's interface!
In regular javascript, and most versions of TypeScript, you should be / must be returning a promise.
function doRequestNormal(): Promise<any> {
return fetch(...).then(...);
}
function someOtherMethodNormal() {
// do some code here
doRequestNormal.then(() => {
// continue your execution here after the request
});
}
In newer versions of typescript, there's async/await keyword support - so instead it might look like this:
async function doRequestAsync() {
var result = await fetch(...);
// do something with request;
return result;
}
async function someOtherMethodAsync() {
// do some code here
var json = await doRequestAsync();
// continue some code here
}
Keep in mind, doRequestAsync still returns a Promise under the hood - but when you call it, you can use await to pretend that you're blocking on it instead of needing to use the .then( callback. If you call an async method from a non-async method, you'll still need to use the callbacks as normal.
this is how I do it:
type Payload = {
id: number
}
type ReturnType = number
export const functionThatHasNumberType = async (
payload: Payload
): Promise<ReturnType> => {
let url = `/api/${payload.id}`
return await axios.get(url)
}

Categories

Resources