问题
I want to make a call to a server that can return an authorization fail (401) with Angular2's HTTP class.
The flow of the request should look like that:
- The user makes a request to the server with myService.getSomething().subscribe()
- If the server returns a 401: open a modal window asking the user for his credentials.
- The user successfully log back into the application
- The modal closes and executes a callback
- The callback should retry the initial request (myService.getSomething().subscribe())
Here is what I have for the moment:
export class MyService {
// ...
public getSomething(): Observable<Response> {
return this.http.get(url, options).catch((res: any, ob: any) => this.errorHandler(res, ob));
}
public errorHandler(res: Response, ob: Observable<any>): Observable<Response> {
if (res.status === 401) {
this.modalService.open(new ModalConfig({
content: LoginModalComponent,
close: () => { ob.retry(1); console.log("weow") } // <=close is the callback that should initiate the retry action on the initial request.
}));
}
else {
return Observable.throw(res.json());
}
}
}
doSomething() is used like that: doSomething().map((r) => r.json()).subscribe((r) => ....)
Update 1
I modified my code to look like @Thierry Templier's solution.
private errorHandler(res: Response, ob: Observable<any>): Observable<Response> {
if (res.status === 401) {
let closedSubject = new Subject();
this.modalService.open(new ModalConfig({
content: LoginModalComponent,
close: () => { closedSubject.next(res);} // I also tried .complete(), .next(null), .next(true), .next(false)
}));
return ob.retryWhen(() => closedSubject);
}
else {
return Observable.throw(res.json());
}
}
Sadly it still doesn't work. The retryWhen is executed right away and doesn't wait for closedSubject.next() to be called. Therefore it starts an infinite loop, spamming the original Observable (the getSomething() function).
Update 2
I created a plunker to demonstrate the infinite loop:
https://plnkr.co/edit/8SzmZlRHvi00OIdA7Bga
Warning: running the plunker will spam your console with the string 'test'
Update 3
Following Thierry's correct answer, I tried to find a way to not use the source field since it is protected. After asking on rxjs's issue tracker to make the field public, a contributor replied with a better solution.
public get(url: string, options?: RequestOptionsArgs): Observable<Response> {
return super.get(url, options).retryWhen((errors: any) => this.errorHandler(errors));
}
private errorHandler(errors): any {
return errors.switchMap((err) => {
if (err.status === 401) {
let closedSubject = new Subject();
this.modalService.open(new ModalConfig({
content: LoginModalComponent,
close: () => { closedSubject.next(err); }
}));
return <any>closedSubject;
}
else {
return Observable.throw(err.json());
}
});
}
I avoid using .catch so I don't have to use the source field.
回答1:
I think that you need to return something an observable even in the case of the 401 error:
public errorHandler(res: Response, ob: Observable<any>): Observable<Response> {
if (res.status === 401) {
let closedSubject = new Subject();
this.modalService.open(new ModalConfig({
content: LoginModalComponent,
close: () => {
closedSubject.next();
}));
return ob.retryWhen(() => closedSubject);
}
else {
return Observable.throw(res.json());
}
}
See this article for more details: https://jaxenter.com/reactive-programming-http-and-angular-2-124560.html.
Edit
The problem is that the second parameter of the catch
callback isn't the source observable. This source observable corresponds to the value of its source
property:
return ob.source.retryWhen((errors) => closedSubject);
See the working plunkr: https://plnkr.co/edit/eb2UdF9PSMhf4Dau2hqe?p=preview.
回答2:
I guess retryWhen
operator should help.
回答3:
I believe the solution has changed a little, since in the news versions of Angular we must use the pipe()
method. So I decided to use a custom operator solution. One good thing is the handleError()
method could be exported as a global function and then be used in more than one service.
See this solution for more details: https://blog.angularindepth.com/retry-failed-http-requests-in-angular-f5959d486294
export class MyService {
// ...
public getSomething(): Observable<Response> {
return this.http.get(url, options).pipe(this.handleError('Maybe your a custom message here'));
}
private handleError(errorMessage: string) {
return (source: Observable<any>) => source.pipe(
retryWhen(errors => errors.pipe(
mergeMap((errorResponse: HttpErrorResponse) => {
console.error(errorMessage);
if (errorResponse.status === 401) {
const closedSubject = new Subject();
this.modalService.open(new ModalConfig({
content: LoginModalComponent,
close: () => {
closedSubject.next();
}
}));
return closedSubject;
}
return throwError(errorResponse);
})
))
);
}
}
来源:https://stackoverflow.com/questions/36654920/how-to-catch-an-error-on-a-request-then-open-a-modal-then-retry-when-modal-clo