【问题标题】:Angular 7: Await function in interceptorAngular 7:拦截器中的等待函数
【发布时间】:2019-10-15 00:18:50
【问题描述】:

我在我的第一个 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。在这一点上我很困惑。

【问题讨论】:

    标签: angular typescript angular7


    【解决方案1】:

    您应该使用 RxJS 'from' 运算符将您的 Promise 转换为 observable,而不是尝试返回异步 Promise,如本文所述: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);
    }
    

    希望这会有所帮助!

    【讨论】:

    • @GiovanniTerlingen 这一次只能处理一个请求。在处理多次调用和身份验证错误时,您的 refreshToken 方法也会被多次调用。
    • @Pilatus,很有趣。我能做些什么呢?随意添加您的答案。
    • 您可以将令牌放入行为主题中(这样您仍然可以返回可观察对象并具有最新值)。接下来,您应该自动刷新您的令牌(在 x 分钟后,但在到期之前。请参阅 auth0.com/docs/quickstart/spa/angular2/05-token-renewal 以获取代码示例)
    • @Mr.wiseguy 你是对的,唯一的问题是用户在令牌过期后返回时。在intercepter 中发出的请求仍然需要等待初始的refreshtoken 调用。
    【解决方案2】:

    Arwin 解决方案运行良好,但仅适用于同时发送一个请求的环境。

    为了让它工作,将 refreshToken 方法保存到带有管道 shareObservable 中。这将允许多个订阅者但只有一个结果。

    next.handle(request) 方法包装在另一个Subject&lt;any&gt; 中并返回主题。 如果请求触发不是401 错误调用subject.error 的错误调用。

    在刷新令牌后调用this.updateTokenAndRetry(request, next, currentUser, token).subscribe(result =&gt; 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);
    }
    

    【讨论】:

      猜你喜欢
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 2019-08-03
      • 2018-09-20
      • 2023-03-13
      • 1970-01-01
      • 2019-09-03
      • 2018-06-13
      相关资源
      最近更新 更多