我在第一个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
。在这一点上我很困。
答案 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解决方案效果很好,但仅在一次发送一个请求的环境中有效。
为了使此工作生效,请使用管道refreshToken
将Observable
方法保存到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);
}