如何捕获请求中的错误,然后打开模态,然后在模态使用RxJS关闭时重试

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

本文关键字:模态 然后 RxJS 重试 请求 何捕获 错误      更新时间:2023-09-26

我想调用一个服务器,该服务器可以使用Angular2的HTTP类返回授权失败(401)。

请求的流程应该是这样的:

  • 用户使用myService.getSomething().subscribe()向服务器发出请求
  • 如果服务器返回401:打开一个模式窗口,询问用户的凭据
  • 用户成功登录回应用程序
  • 模态关闭并执行回调
  • 回调应该重试初始请求(myService.getSomething().subscribe())

以下是我目前拥有的:

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()是这样使用的:doSomething().map((r) => r.json()).subscribe((r) => ....)

更新1

我修改了代码,使其看起来像@Thierry Templier的解决方案。

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());
    }
}

遗憾的是,它仍然不起作用。retryWhen立即执行,并且不等待调用closedSubject.next()。因此,它开始了一个无限循环,滥发原始Observable(getSomething()函数)。

更新2

我创建了一个plunker来演示无限循环:

https://plnkr.co/edit/8SzmZlRHvi00OIdA7Bga

警告:运行plunker将用字符串"test"向您的控制台发送垃圾邮件

更新3

按照Thierry的正确答案,我试图找到一种方法,不使用源字段,因为它是受保护的。在请求rxjs的问题跟踪器公开该字段后,一位贡献者给出了更好的解决方案。

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());
        }
    });
}

我避免使用.catch,这样就不必使用源字段。

我认为即使在401错误的情况下,也需要返回一些可观察的东西:

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());
    }
}

有关更多详细信息,请参阅本文:https://jaxenter.com/reactive-programming-http-and-angular-2-124560.html.

编辑

问题是catch回调的第二个参数不是可观察的源。此可观测源对应于其source属性的值:

return ob.source.retryWhen((errors) => closedSubject);

参见工作plunkr:https://plnkr.co/edit/eb2UdF9PSMhf4Dau2hqe?p=preview.

我想retryWhen运算符应该会有所帮助。

我相信解决方案已经发生了一些变化,因为在Angular的新版本中,我们必须使用pipe()方法。所以我决定使用自定义运算符解决方案。一个好处是handleError()方法可以作为全局函数导出,然后在多个服务中使用。

有关详细信息,请参阅此解决方案: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);
            })
        ))
    );
}

}