Issue
I have an Ionic/Angular application where I am using NgRx for the state mangement. When I load my data I set various state flags, and then have a selector called isLoading where I just "and" each of the loading flags. This part of it seems to work fine.
However, my loading indicator show and hide is actually presented two async methods.
I have the following code, to show/hide this...
this.subs.sink = this.store$.select(fromApp.isLoading)
.pipe(switchMap(async isLoading => {
this.logger.info(`is loading: ${isLoading}`);
if (isLoading) {
await this.presentDataLoading();
this.logger.info(`is loading presented`);
return of(isLoading);
} else {
await this.dismissLoaderIfPresent();
this.logger.info(`is loading dismissed`);
return of(isLoading);
}
})).subscribe();
And these two methods look like...
protected async presentDataLoading(message? : string): Promise<void> {
if (this.loaderBase != undefined || this.isLoading) {
return;
}
this.isLoading = true;
let loaderMessage = message == undefined ? this.translateBase.instant(ResourceKeys.vals.messageLoadingData) : message;
this.loaderBase = await this.userMessageServiceBase.presentLoadingSpinner(loaderMessage);
this.isLoading = false;
}
protected async dismissLoaderIfPresent() : Promise<void> {
if (this.loaderBase == undefined) {
return;
}
await this.loaderBase.dismiss().catch(error => this.loggerBase.error(`loaderBase.dismiss: ${error}`));
this.loaderBase = undefined;
}
So above I have used the switchMap to try and resolve my race condition, but is does not seem to be helping.
The logging out put is as follows.
9:43:25 is loading: undefined
9:43:25 is loading: true
9:43:25 is loading dismissed
9:43:25 is loading presented
9:43:25 is loading: undefined
9:43:25 is loading dismissed
9:43:30 is loading: undefined
9:43:30 is loading: true
9:43:30 is loading dismissed
9:43:30 is loading: undefined <--- last emitted val is now false. Correct
9:43:30 is loading dismissed
9:43:31 is loading presented <--- But I now have this just being called
So we can see the last value of isLoading to be emitted is undefined (which is correct)
But we see the log statement loading presented is the last to finish, so this is out of order, and I have a stuck spinner.
So I really need the contents of the "switchMap" to be "locked" until the promises have resolved, but I am not sure why switchMap is not doing this, as I am not returning an observable (i.e. via the return of(isLoading); until AFTER the awaits.
Anyone have any idea how I can handle this situation?
[UPDATE1]
Taking notes of answers below, and now using concatMap (so the observables are not cancelled) and converting the promises to observables using from, I now have the following...
this.subs.sink = this.store$.select(fromApp.isLoading)
.pipe(concatMap(isLoading => {
this.logger.info(`is loading: ${isLoading}`);
if (isLoading) {
const obs = from(this.presentDataLoading());
this.logger.info(`is loading presented`);
return obs;
} else {
const obs = from(this.dismissLoaderIfPresent());
this.logger.info(`is loading dismissed`);
return obs;
}
})).subscribe();
So far this seems to be behaving as I want - log output
6:54:58 is loading: undefined
6:54:58 is loading dismissed
6:54:58 is loading: true
6:54:58 is loading presented
6:54:58 is loading: undefined
6:54:58 is loading dismissed
Solution
A Simple Solution
Promises are eager, so they'll run even if nobody is interested in the results. Your promise is running before you even hand it to the switchMap to manage. Also, switchMap may not work as expected with promises since they cannot be cancelled. Perhaps use concatMap instead.
The way to fix this with the smallest change is to defer your promise,so it doesn't start until RxJS subscribes.
this.subs.sink = this.store$.select(
fromApp.isLoading
).pipe(
concatMap(defer(() => async isLoading => {
/* Same code as before */
}))
).subscribe();
If you want to avoid all these edge cases, then stop interleaving Promises and Observables, just pick one. They do interoperate, but they're not designed with that in mind. Observables are lazy and can be cancelled while promises are eager and cannot be cancelled.
If you ever work in a software shop, they'll likely discourage or disallow this style of code. Switching between these two asynchronous libraries should be kept minimal and at the boundaries of your program modules/libraries.
Answered By - Mrk Sef
0 comments:
Post a Comment
Note: Only a member of this blog may post a comment.