Angular 7:拦截器中的等待函数

Posted

技术标签:

【中文标题】Angular 7:拦截器中的等待函数【英文标题】:Angular 7: Await function in interceptor 【发布时间】: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。在这一点上我很困惑。

【问题讨论】:

【参考方案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);

【讨论】:

以上是关于Angular 7:拦截器中的等待函数的主要内容,如果未能解决你的问题,请参考以下文章

Angular 7 错误拦截器 – 原始调用有问题

ASP.NET 4.5 Web API 2.0,JWT 消息处理程序将状态 0 返回到 Angular 7 HTTP 拦截器

Angular 拦截器中的响应标头

Angular 7 - 如何在某些响应状态代码上重试 http 请求?

Angular 5:从拦截器中的 http 响应标头获取授权

在c#中等待grpc拦截器中的延续方法