由于我已经开始使用angular2,我已经设置了我的服务来返回T的Observable。在服务中我会有map()调用,使用这些服务的组件只会使用subscribe()来等待响应。对于这些简单的场景我真的不需要深入研究rxjs所以一切都还可以。
我现在想要实现以下目标:我正在使用带刷新令牌的Oauth2身份验证。我想构建一个所有其他服务都将使用的api服务,并在返回401错误时透明地处理刷新令牌。因此,在401的情况下,我首先从OAuth2端点获取新令牌,然后使用新令牌重试我的请求。以下是可以正常使用的代码:承诺:
request(url: string, request: RequestOptionsArgs): Promise<Response> {
var me = this;
request.headers = request.headers || new Headers();
var isSecureCall: boolean = true; //url.toLowerCase().startsWith('https://');
if (isSecureCall === true) {
me.authService.setAuthorizationHeader(request.headers);
}
request.headers.append('Content-Type', 'application/json');
request.headers.append('Accept', 'application/json');
return this.http.request(url, request).toPromise()
.catch(initialError => {
if (initialError && initialError.status === 401 && isSecureCall === true) {
// token might be expired, try to refresh token.
return me.authService.refreshAuthentication().then((authenticationResult:AuthenticationResult) => {
if (authenticationResult.IsAuthenticated == true) {
// retry with new token
me.authService.setAuthorizationHeader(request.headers);
return this.http.request(url, request).toPromise();
}
return <any>Promise.reject(initialError);
});
}
else {
return <any>Promise.reject(initialError);
}
});
}
在上面的代码中,authService.refreshAuthentication()将获取新令牌并将其存储在localStorage中。 authService.setAuthorizationHeader将设置&#39;授权&#39;标头到先前更新的标记。如果你看一下catch方法,你会看到它返回一个promise(对于刷新令牌),它将最终返回另一个promise(对于请求的实际第二次尝试)。
我试图在不诉诸承诺的情况下这样做:
request(url: string, request: RequestOptionsArgs): Observable<Response> {
var me = this;
request.headers = request.headers || new Headers();
var isSecureCall: boolean = true; //url.toLowerCase().startsWith('https://');
if (isSecureCall === true) {
me.authService.setAuthorizationHeader(request.headers);
}
request.headers.append('Content-Type', 'application/json');
request.headers.append('Accept', 'application/json');
return this.http.request(url, request)
.catch(initialError => {
if (initialError && initialError.status === 401 && isSecureCall === true) {
// token might be expired, try to refresh token
return me.authService.refreshAuthenticationObservable().map((authenticationResult:AuthenticationResult) => {
if (authenticationResult.IsAuthenticated == true) {
// retry with new token
me.authService.setAuthorizationHeader(request.headers);
return this.http.request(url, request);
}
return Observable.throw(initialError);
});
}
else {
return Observable.throw(initialError);
}
});
}
上面的代码没有达到我的预期:在200响应的情况下,它正确地返回响应。但是,如果它捕获401,它将成功检索新令牌,但订阅将最终检索一个observable而不是响应。我猜这是应该重试的未执行的Observable。
我意识到将工作的承诺方式转换为rxjs库可能不是最好的方法,但我还没有能够掌握&#34;一切都是一个流&#34;事情。我已经尝试了一些涉及flatmap的其他解决方案,重试等等......但是没有达到目标,所以一些帮助表示赞赏。
答案 0 :(得分:24)
通过快速查看您的代码,我会说您的问题似乎是您没有压扁从Observable
服务返回的refresh
。
catch
运算符期望您将返回Observable
,它将连接到失败的Observable的末尾,以便下游Observer
不知道差异。< / p>
在非401情况下,您通过返回重新抛出初始错误的Observable正确执行此操作。但是,在刷新案例中,您将返回Observable
生成更多 Observables
而不是单个值。
我建议您将刷新逻辑更改为:
return me.authService
.refreshAuthenticationObservable()
//Use flatMap instead of map
.flatMap((authenticationResult:AuthenticationResult) => {
if (authenticationResult.IsAuthenticated == true) {
// retry with new token
me.authService.setAuthorizationHeader(request.headers);
return this.http.request(url, request);
}
return Observable.throw(initialError);
});
flatMap
会将中间Observables
转换为单个流。
答案 1 :(得分:11)
在最新版本的RxJ中,flatMap
运算符已重命名为mergeMap
。
答案 2 :(得分:1)
我创建了这个demo来弄清楚如何使用rxjs处理刷新令牌。它这样做:
此演示不会进行实际的HTTP调用(使用Observable.create
模拟它们。)
相反,使用它来学习如何使用catchError
和retry
运算符来修复问题(第一次访问令牌失败),然后重试失败的操作(API调用)。