The code below intercepts any http request in my ionic app and adds a loading animation along with retry logic. The issue is that sometimes if the request is fast, the finalize() callback is getting called before the loading animation has been created thus never removing the loading animation.
I believe the solution is to use async and await on the creation of the loading animation so the finalize() callback is not called until the animation has been created but I've failed to make it work.
export class HttpRequestInterceptor implements HttpInterceptor {
loading: any;
loaderToShow: any;
constructor(
private loadingCtrl: LoadingController,
private alertCtrl: AlertController,
private authService: AuthService) {
}
intercept(request: HttpRequest<any>, next: HttpHandler): Observable<HttpEvent<any>> {
this.loadingCtrl.getTop().then(hasLoading => {
if (!hasLoading) {
this.loading = this.loadingCtrl.create({
spinner: 'circular',
translucent: true
}).then(loading => loading.present());
}
})
return next.handle(request).pipe(
catchError(err => {
if (err instanceof HttpErrorResponse) {
console.log('error');
console.log(err);
this.loadingCtrl.getTop().then(hasLoading => {
if (hasLoading) {
this.loadingCtrl.dismiss();
}
});
switch ((<HttpErrorResponse>err).status) {
//Login
case 401:
this.PresentFailedAlert(err.error['message']);
return EMPTY;
case 409:
this.PresentFailedAlert(err.error['message']);
return EMPTY;
default:
return throwError(err);
}
} else {
return throwError(err);
}
}),
retryWhen(err => {
let retries = 1;
return err.pipe(
delay(3000),
map(error => {
if (retries === 3) {
throw error;
}
return error;
})
)
}),
//After our all retries failed. They might not have internet or server is down
catchError(err => {
console.log(err);
this.PresentFailedAlert(err.error['message']);
return EMPTY;
}),
//After call is done
finalize(() => {
delay(500);
this.loadingCtrl.getTop().then(hasLoading => {
if (hasLoading) {
this.loadingCtrl.dismiss();
}
})
})
);
}
CodePudding user response:
Can you try use async function in finalize block
finalize(async() => {
delay(500);
await this.loadingCtrl.getTop().then(hasLoading => {
if (hasLoading) {
this.loadingCtrl.dismiss();
}
})
})
CodePudding user response:
Since the last catchError
returns Empty
observable not an error, you can add a higher-order mapping operator like switchMap
or switchMapTo
after it to get the result of the promise, and do what you want without finalize
.
You can try the following:
return next.handle(request).pipe(
catchError((err) => {
if (err instanceof HttpErrorResponse) {
console.log('error');
console.log(err);
this.loadingCtrl.getTop().then((hasLoading) => {
if (hasLoading) {
this.loadingCtrl.dismiss();
}
});
switch ((<HttpErrorResponse>err).status) {
//Login
case 401:
this.PresentFailedAlert(err.error['message']);
return EMPTY;
case 409:
this.PresentFailedAlert(err.error['message']);
return EMPTY;
default:
return throwError(err);
}
} else {
return throwError(err);
}
}),
retryWhen((err) => {
let retries = 1;
return err.pipe(
delay(3000),
map((error) => {
if (retries === 3) {
throw error;
}
return error;
})
);
}),
//After our all retries failed. They might not have internet or server is down
catchError((err) => {
console.log(err);
this.PresentFailedAlert(err.error['message']);
return EMPTY;
}),
//After call is done
delay(500),
// use `from` to convert promise to observable.
switchMapTo(from(this.loadingCtrl.getTop())),
// check hasLoading and return EMPTY observable after that.
switchMap((hasLoading) => {
if (hasLoading) {
this.loadingCtrl.dismiss();
}
return EMPTY;
})
// //After call is done
// finalize(() => {
// delay(500);
// this.loadingCtrl.getTop().then((hasLoading) => {
// if (hasLoading) {
// this.loadingCtrl.dismiss();
// }
// });
// })
);