How to prevent Zone Aware Promise in angular?
The following code is returning ZoneAwarePromise
const versions = await itemDetails.imageSamples.map(({ item, images }) => {
const mapped = Promise.all(
images.map((image) =>
this.pushImage(image, `items/${id}/images/`)
)
);
return { item, images };
});
// here I'm mapping twice. & I suspect this double mapping is causing the Zone Aware Promise
// I tried this way also:
const versions = await Promise.all( itemDetails.imageSamples.map(({ item, images }) => {
const mapped = Promise.all( ---> // this inside mapping is causing zone aware promise
images.map((image) =>
this.pushImage(image, `items/${id}/images/`)
)
);
return { item, images };
}));
The following code is returning the promise correctly (i.e without any Zone Aware Promise).
const samples = await Promise.all(
itemDetails.samples.map((sample) =>
this.pushImage(sample, `items/${id}/samples/`)
)
);
// here I'm mapping only once. So there is no Zone Aware Promise.
This is the push Image async function.
async pushImage(image, basePath) {
const imgId = this.angularDatabase.createPushId();
const route = `${basePath}${imgId}`;
const imageRef = this.angularFireStorage.ref(route);
return await concat(
imageRef.put(image.image).snapshotChanges().pipe(ignoreElements()),
defer(() => imageRef.getDownloadURL())
)
.pipe(
map(
(url) => (
{ ...image, url }
)
)
)
.toPromise();
}
SO it seems like mapping twice is creating issue
Can someone suggest a workaround?
Update
const promises = Promise.all(
itemDetails.promises.map(({ details, images }) => ({
details,
images: Promise.all(
images.map((image) =>
this.pushImage(image, `items/${id}/images/`)
)
),
}))
);
Related
I have an effect that should call two different APIs (API1 and API2).
Here's the effect
$LoadKpiMission = createEffect(() =>
this.actions$.pipe(
ofType<any>(EKpiActions.GetMissionsByStation),
mergeMap(action =>
this.apiCallsService.getKpi(action.payload, '2016-04-18').pipe(
map(trips => ({ type: EKpiActions.GetMissionsSuccess, payload: trips })),
catchError(() => EMPTY)
)
)
)
);
Here's the structure of the service
getKpi(station: number, date: string) {
let Kpi = `http://192.168.208.25:8998/api/scheduling/circulation_by_date_and_station?orig=${station}&date=${date}`;
return this.http.get<ISchedules>(API1).pipe(
map(data => {
return this.formatDataToKpi1(data);
})
);
}
However, I have to retrieve additional data from API2 and merge it with the data returned from API1.
I should do that inside the formatDataToKpi1 function.
I would like to know how to run requests in parallel and pass the returned responses to formatDataToKpi1 and do treatment then return to the effect ?
You can make use of the forkJoin RxJS operator.
As stated on the documentation,
When all observables complete, emit the last emitted value from each.
This way, when the observables from both requests have been completed, it will be returned, and you can carry out the subsequent operations.
$LoadKpiMission = createEffect(() =>
this.actions$.pipe(
ofType<any>(EKpiActions.GetMissionsByStation),
mergeMap(action =>
const getKpi = this.apiCallsService.getKpi(action.payload, '2016-04-18');
const getKpi2 = this.apiCallsService.getKpi2();
forkJoin(getKpi, getKpi2).subscribe(([res1, res2] => {
// do the rest here
});
)
)
);
EDIT: Looks like I have initially misunderstood your question - Was a bit confused by the variable names
getKpi(station: number, date: string) {
let Kpi = `http://192.168.208.25:8998/api/scheduling/circulation_by_date_and_station?orig=${station}&date=${date}`;
const api1 = this.http.get<ISchedules>(API1);
const api2 = this.http.get<ISchedules>(API2);
return forkJoin(api1, api2).pipe(
map(data => {
return this.formatDataToKpi1(data);
})
);
}
I have an Epic. I want to pass 2 HTTP Get Requests. They are both Promised based. But it only bring data for the first processed one. THE Epic:
const processorsListEpic = (action$, store, deps) =>
action$.ofType(Type.LIST_ATTEMPT).pipe(
switchMap(() =>
observableFromHttpPromise(
deps.getList(store), // This bring data
deps.getTargets(store) // This doesn't
).pipe(
mergeMap((listResult, targetResult) => {
console.log('Target:', targetResult.data);
console.log('List', listResult.data);
return of(
R.mergeAll(
Actions.ListSuccess(listResult && listResult.data),
Actions.TargetsSuccess(targetResult && targetResult.data)
)
);
}),
catchError(error => of(Actions.ListFailure(error)))
)
)
);
The function observableFromHttpPromise is the following:
// From is from rxjs
export const observableFromHttpPromise = promise => from(promise);
Any Ideas? If I change the order of the requests, the other data are there..
Try this
observableFromHttpPromise(Promise.all(
deps.getList(store),
deps.getTargets(store),
))
I have a service to get a list from server. But in this list I need to call another service to return the logo img, the service return ok, but my list remains empty. What i'm did wrong ?
I tried to use async/await in both services
I tried to use a separate function to get the logos later, but my html don't change.
async getOpportunitiesByPage(_searchQueryAdvanced: any = 'active:true') {
this.listaOportunidades = await this._opportunities
.listaOportunidades(this.pageSize, this.currentPage, _searchQueryAdvanced)
.toPromise()
.then(result => {
this.totalSize = result['totalElements'];
return result['content'].map(async (opportunities: any) => {
opportunities.logoDesktopUrl = await this.getBrand(opportunities['brandsUuid']);
console.log(opportunities.logoDesktopUrl);
return { opportunities };
});
});
this.getTasks(this.totalSize);
}
No errors, just my html don't change.
in my
console.log(opportunities.logoDesktopUrl);
return undefined
but in the end return filled.
info:
Angular 7
server amazon aws.
await is used to wait for promise.
You should return promise from getBrand if you want to wait for it in getOpportunitiesByPage.
Change the getBrand function as following.
getBrand(brandsUuid): Observable<string> {
this.brandService.getById(brandsUuid).pipe(map(res => {
console.log(res.logoDesktopUrl); return res.logoDesktopUrl;
}))
}
Change opportunities.logoDesktopUrl = await this.getBrand(opportunities['brandsUuid']); to opportunities.logoDesktopUrl = await this.getBrand(opportunities['brandsUuid']).toPromise();
Please make sure you imported map from rxjs/operators.
At first,when you await, you should not use then.
At second, async/await runs only with Promises.
async getOpportunitiesByPage(_searchQueryAdvanced: any = 'active:true') {
const result = await this._opportunities
.listaOportunidades(this.pageSize, this.currentPage, _searchQueryAdvanced)
.toPromise();
this.totalSize = result['totalElements'];
this.listaOportunidades = result['content'].map(async (opportunities: any) => {
opportunities.logoDesktopUrl = await this.getBrand(opportunities['brandsUuid']);
console.log(opportunities.logoDesktopUrl);
return opportunities;
});
this.getTasks(this.totalSize);
}
getBrand(brandsUuid) {
return new Promise((resolve, reject) => {
this.brandService.getById(brandsUuid).subscribe(res => {
console.log(res.logoDesktopUrl);
return resolve(res.logoDesktopUrl);
}, err => {
return reject(err);
});
});
}
But, because rxjs is a used in Angular, you should use it instead of async/await :
getOpportunitiesByPage: void(_searchQueryAdanced: any = 'active:true') {
this._opportunities.listaOportunidades(this.pageSize, this.currentPage, _searchQueryAdvanced).pipe(
tap(result => {
// we do that here because the original result will be "lost" after the next 'flatMap' operation
this.totalSize = result['totalElements'];
}),
// first, we create an array of observables then flatten it with flatMap
flatMap(result => result['content'].map(opportunities => this.getBrand(opportunities['brandsUuid']).pipe(
// merge logoDesktopUrl into opportunities object
map(logoDesktopUrl => ({...opportunities, ...{logoDesktopUrl}}))
)
),
// then we make each observable of flattened array complete
mergeAll(),
// then we wait for each observable to complete and push each result in an array
toArray()
).subscribe(
opportunitiesWithLogoUrl => {
this.listaOportunidades = opportunitiesWithLogoUrl;
this.getTasks(this.totalSize);
}, err => console.log(err)
);
}
getBrand(brandsUuid): Observable<string> {
return this.brandService.getById(brandsUuid).pipe(
map(res => res.logoDesktopUrl)
);
}
Here is a working example on stackblittz
There might be a simpler way to do it but it runs :-)
I'm trying to prefetch multiple image before navigating to another screen, but returnedStudents all undefined.
prepareStudentImages = async (students) => {
let returnedStudents = students.map(student => {
Image.prefetch(student.image)
.then((data) => {
...
})
.catch((data) => {
...
})
.finally(() => {
return student;
});
});
await console.log(returnedStudents); // ----> all items undefined
}
There are a couple of things to fix with this:
1) Your map() function does not return anything. This is why your console log is undefined.
2) Once your map functions work, you are logging an array of promises. To deal with multiple promises (an array), you can use Promise.all().
So I think to fix this, you can do:
prepareStudentImages = async (students) => {
const returnedStudents = students.map(student =>
Image.prefetch(student.image)
.then((data) => {
...
})
.catch((data) => {
...
})
.finally(() => {
return student
})
)
console.log(returnedStudents) // log the promise array
const result = await Promise.all(returnedStudents) // wait until all asyncs are complete
console.log(result) // log the results of each promise in an array
return result
}
I am new to rxjs and redux-observable. Trying to create an epic that allows me to do multiple parallel ajax calls and dispatches respective actions on success:
const loadPosters = (action$) => action$.pipe(
ofType(Types.LOAD_FILMS_SUCCESS),
switchMap(({ films }) =>
forkJoin(films.map(film =>
ajax
.getJSON(`https://api.themoviedb.org/3/search/movie?query=${film.title}`)
.pipe(
map(response => {
const [result] = response.results;
const poster = `http://image.tmdb.org/t/p/w500/${result.poster_path}`;
return Creators.savePoster(film, poster);
})
),
))
),
);
Creators.savePoster() is an action creator for an action named SAVE_POSTER. But, whenever i run my application, no such action is dispatched. Instead i get an error message in browser console:
Uncaught Error: Actions must be plain objects. Use custom middleware
for async actions.
edit
Tried a simplified version without forkJoin, sadly yielding the same result:
const loadPosters = (action$) => action$.pipe(
ofType(Types.INIT_SUCCESS),
mergeMap(({ films }) =>
films.map(film =>
ajax
.getJSON(`https://api.themoviedb.org/3/search/movie?query=${film.title}`)
.pipe(
map(response => {
const [result] = response.results;
const poster = `http://image.tmdb.org/t/p/w500/${result.poster_path}`;
console.log(Creators.savePoster(film, poster));
return Creators.savePoster(film, poster);
})
)
),
),
);
Appendix
Just for reference, I have another epic which does a simple ajax call which works fine:
const loadFilms = action$ => action$.pipe(
ofType(Types.INIT_REQUEST),
mergeMap(() =>
ajax
.getJSON('https://star-wars-api.herokuapp.com/films')
.pipe(
map(response => Creators.initSuccess(response))
),
),
);
The problem is that i don't return an Observable in my inner map. Changing:
return Creators.savePoster(film, poster);
to
return of(Creators.savePoster(film, poster));
makes it work.
By the way, if used with forkJoin it's also possible (and in my case better) to take the mapped results after all requests resolved and dispatch a single action instead of multiple ones:
const loadPosters = (action$) => action$.pipe(
ofType(Types.INIT_SUCCESS),
mergeMap(({ films }) =>
forkJoin(
films.map(film =>
ajax
.getJSON(`https://api.themoviedb.org/3/search/movie?query=${film.title}`)
.pipe(
map(response => ({ film, response }))
)
),
)
),
mergeMap(data => {
const posters = data.reduce((acc, { film, response}) => ({
...acc,
[film.id]: `http://image.tmdb.org/t/p/w500/${response.results[0].poster_path}`,
}), {});
return of(Creators.savePosters(posters));
})
);
In fact this is my favorite solution so far.