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
方法保存到带有管道 share
的 Observable
中。这将允许多个订阅者但只有一个结果。
将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);
【讨论】:
以上是关于Angular 7:拦截器中的等待函数的主要内容,如果未能解决你的问题,请参考以下文章
ASP.NET 4.5 Web API 2.0,JWT 消息处理程序将状态 0 返回到 Angular 7 HTTP 拦截器
Angular 7 - 如何在某些响应状态代码上重试 http 请求?