Angular 7:拦截器中的Await函数

时间:2019-05-29 11:35:17

标签: angular typescript angular7

我在第一个Angular应用程序中构建了一个错误拦截器,这对我来说是全新的。发生401响应代码时,拦截器会尝试刷新Firebase授权令牌。因此,我编写了以下代码:

@Injectable()
export class ErrorInterceptor implements HttpInterceptor {
constructor(private authService: AuthService, private alertService: AlertService) { }

intercept(request: HttpRequest<any>, next: HttpHandler): Observable<HttpEvent<any>> {
    return next.handle(request).pipe(
        catchError(err => {
            if (err.status === 401) {
                let user = localStorage.getItem('currentUser');
                if (!user) {
                    this.logout(false);
                    return throwError(err.error);
                }
                let currentUser = JSON.parse(user);
                if (!currentUser || !currentUser.stsTokenManager || !currentUser.stsTokenManager.accessToken) {
                    this.logout(false);
                    return throwError(err.error);
                }
                const reference = this;
                this.authService.getToken(currentUser, true).then(t => {
                    // How do I await and return this properly?
                    return reference.updateTokenAndRetry(request, next, currentUser, t);
                }); // Get token and refresh
            }
            this.alertService.showAlert({
                text: 'Fout tijdens het verzenden van het verzoek',
            });
            return throwError(err.error);
        })
    );
}

updateTokenAndRetry(request: HttpRequest<any>, next: HttpHandler, currentUser: any, token: string): Observable<HttpEvent<any>> {
    // Update local stored user
    currentUser.stsTokenManager.accessToken = token;
    localStorage.setItem('currentUser', JSON.stringify(currentUser));

    // Add the new token to the request
    request = request.clone({
        setHeaders: {
            Authorization: token,
        },
    });

    return next.handle(request);
}

令牌刷新得很好。但是,刷新后并不会执行网络调用,reference.updateTokenAndRetry(request, next, currentUser, t);应该会这样做。

我认为这样做的原因是this.authService.getToken(currentUser, true)返回一个Promise(这是Firebase插件,不能更改)。我想返回return reference.updateTokenAndRetry(request, next, currentUser, t);,但由于它位于异步功能块中,因此无法实现。

如何等待或返回下一个网络呼叫?我无法使用intercept函数async。在这一点上我很困。

2 个答案:

答案 0 :(得分:4)

您应该使用RxJS'from'运算符将您的诺言转换为可观察到的诺言,而不是尝试返回异步诺言,如本文Convert promise to observable中所述。

这将为拦截器提供正确的Observable>返回类型。

您的代码如下所示(假设您一次只发送一个请求):

intercept(request: HttpRequest<any>, next: HttpHandler): Observable<HttpEvent<any>> {
    return next.handle(request).pipe(
        catchError(err => {
            if (err.status === 401) {
                let user = localStorage.getItem('currentUser');
                if (!user) {
                    this.logout(false);
                    return throwError(err.error);
                }
                let currentUser = JSON.parse(user);
                if (!currentUser || !currentUser.stsTokenManager || !currentUser.stsTokenManager.accessToken) {
                    this.logout(false);
                    return throwError(err.error);
                }
                // Return a newly created function here
                return this.refreshToken(currentUser, request, next);
            }
            this.alertService.showAlert({
                text: 'Fout tijdens het verzenden van het verzoek',
            });
            return throwError(err.error);
        })
    );
}

refreshToken(currentUser: any, request: any, next: any) {
    // By making use of the from operator of RxJS convert the promise to an observable
    return from(this.authService.getToken(currentUser, true)).pipe(
        switchMap(t => this.updateTokenAndRetry(request, next, currentUser, t))
    )
}

updateTokenAndRetry(request: HttpRequest<any>, next: HttpHandler, currentUser: any, token: string): Observable<HttpEvent<any>> {
    // Update local stored user
    currentUser.stsTokenManager.accessToken = token;
    localStorage.setItem('currentUser', JSON.stringify(currentUser));

    // Add the new token to the request
    request = request.clone({
        setHeaders: {
            Authorization: token,
        },
    });

    return next.handle(request);
}

希望这会有所帮助!

答案 1 :(得分:1)

Arwin解决方案效果很好,但仅在一次发送一个请求的环境中有效。

为了使此工作生效,请使用管道refreshTokenObservable方法保存到share中。这将允许多个订阅者,但只有一个结果。

next.handle(request)方法包装到另一个Subject<any>中,然后返回主题。 如果请求触发了不是401错误调用subject.error的错误调用。

刷新令牌调用this.updateTokenAndRetry(request, next, currentUser, token).subscribe(result => subject.next(result);后,请确保将请求返回给初始订户。

下面的代码是伪代码,应该适合您的情况。

refreshTokenObservable: Observable<any>;

intercept(request: HttpRequest<any>, next: HttpHandler): Observable<HttpEvent<any>> {

    let subject = new Subject<any>();

    next.handle(request).pipe(
        catchError(err => {
            if (err.status === 401) {
                let user = localStorage.getItem('currentUser');
                if (!user) {
                    this.logout(false);
                    subject.error(err.error);
                    return;
                }
                let currentUser = JSON.parse(user);
                if (!currentUser || !currentUser.stsTokenManager || !currentUser.stsTokenManager.accessToken) {
                    this.logout(false);
                    subject.error(err.error);
                    return;
                }
                // Return a newly created function here
                this.refreshToken(currentUser).subscribe(token => {

                    this.updateTokenAndRetry(request, next, currentUser, token).subscribe(result => subject.next(result);
                    this.refreshTokenObservable = null; // clear observable for next failed login attempt
                });
            }
            this.alertService.showAlert({
                text: 'Fout tijdens het verzenden van het verzoek',
            });
            subject.error(err.error);
        })
    ).subscribe(result => subject.next(result));

    return subject.asObservable();
}

refreshToken(currentUser: any) {

    if(this.refreshTokenObservable == null)
    {
        // By making use of the from operator of RxJS convert the promise to an observable
        this.refreshTokenObservable = from(this.authService.getToken(currentUser, true)).pipe(share());
    }

    return this.refreshTokenObservable;
}

updateTokenAndRetry(request: HttpRequest<any>, next: HttpHandler, currentUser: any, token: string): Observable<HttpEvent<any>> {
    // Update local stored user
    currentUser.stsTokenManager.accessToken = token;
    localStorage.setItem('currentUser', JSON.stringify(currentUser));

    // Add the new token to the request
    request = request.clone({
        setHeaders: {
            Authorization: token,
        },
    });

    return next.handle(request);
}