How to catch an error on a Request, then open a modal, then retry when modal closes with RxJS

淺唱寂寞╮ 提交于 2019-12-20 10:46:15

问题


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

易学教程内所有资源均来自网络或用户发布的内容,如有违反法律规定的内容欢迎反馈
该文章没有解决你所遇到的问题?点击提问,说说你的问题,让更多的人一起探讨吧!