2016-01-20 22 views
38

由於我已經開始使用angular2,我已經設置了我的服務來返回T的Observable。在服務中,我將使用map()調用,並且使用這些服務的組件僅使用subscribe()等待迴應。對於這些簡單的場景,我並不需要深入研究rxjs,因此一切正常。使用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會將'授權'標頭設置爲先前更新的標記。如果你看看catch方法,你會發現它會返回一個承諾(對於刷新令牌),最終會返回另一個承諾(對於請求的第二次嘗試)。

我試圖做到這一點,而不訴諸承諾:

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庫可能不是最好的方法,但我還沒有能夠掌握「一切都是流」的東西。我嘗試了一些其他解決方案,涉及flatmap,retryWhen等......但沒有太多,所以一些幫助表示讚賞。

回答

22

從快速查看您的代碼,我會說你的問題似乎是,你沒有扁平從refresh服務返回Observable

catch運營商期望,你會返回一個Observable它將串聯到失敗的可觀測的結束,使下游Observer不知道其中的差別。

在非401的情況下,您通過返回反映初始錯誤的Observable正確地執行此操作。但是在更新情況下,您要退回Observable生產更多Observables而不是單個值。

我建議你更改刷新邏輯是:

​​

flatMap將中間Observables轉換成一個單一的數據流。

+0

哈,這工作:)我試過使用flatMap方法,但顯然不是以正確的方式。 Thanx Paul! Ps:我在rxjs中發現的東西很雜亂,就是他們傾向於將這些方法隱藏在具有不同名稱的文件中。爲了使flatMap方法起作用,我必須導入「mergeMap」文件... – Davy

+0

@Davy我可以問你正在使用哪個版本?如果是來自ReactiveX的項目,它目前仍處於測試階段,所以在完成所有功能時文檔仍然有點欠缺。 – paulpdaniels

+0

這一個:https://github.com/ReactiveX/RxJS(「rxjs」npm包)。 – Davy

9

在RxJs的最新版本中,flatMap運營商已更名爲mergeMap