2017-08-09 48 views
2

我有這樣的功能:角:有副作用並從方法返回可觀察

/** 
    * Attempt login with provided credentials 
    * @returns {Observable<number>} the status code of HTTP response 
    */ 
    login(username: string, password: string): Observable<number> { 
    let body = new URLSearchParams(); 
    body.append('grant_type', 'password'); 
    body.append('username', username); 
    body.append('password', password); 

    return this.http.post(Constants.LOGIN_URL, body, null) 
     .do(res => { 
     if (res.status == 200) { 
      this.authTokenService.setAuthToken(res.json().auth_token); 
     } 
     }) 
     .map(res => res.status) 
     .catch(err => { return Observable.throw(err.status) }); 
    } 

這個函數嘗試進行登錄,並返回Observable<number>調用方可以使用以便被通知的HTTP代碼的響應。

這似乎工作,但我有一個問題:如果調用者只是調用該函數而不訂閱返回的Observable,do函數未被調用,並且收到的授權令牌未被保存。

do狀態文檔:

注:這是不同的可觀測訂閱。如果由do返回的Observable未訂閱,Observer指定的副作用 將不會發生。因此,只是在現有的執行間諜 ,它不會觸發執行發生像訂閱 那樣。

我想實現的是,無論login()的調用者是否訂閱,都會產生這種副作用。我該怎麼辦?

+0

此方法返回可觀察值。如果你不打算訂閱它的迴應,那麼調用它有什麼意義? (如果您不訂閱,您不會提出此請求) – echonax

+0

@echonax,此方法正在使用中。可能有多個客戶 - 其中一些客戶會訂閱,其他客戶則不會。我不希望服務的功能依賴於外部使用的語義。 – Vasiliy

+0

你可以在'login()'方法內調用'subscribe()'方法 – martin

回答

2

正如@Maximus所解釋的那樣,根據設計,冷Observable(如http調用)在訂閱它們之前不會發射任何數據。所以你的.do()回調將永遠不會被調用。

另一方面,熱可觀測量將發出數據而不關心是否有用戶。

您可以使用publish()操作符將您的感冒Observable轉換爲ConnectableObservable。該Observable將在其調用connect()方法時開始發射。

login(username: string, password: string): Observable <number> { 
    let body = new URLSearchParams(); 
    body.append('grant_type', 'password'); 
    body.append('username', username); 
    body.append('password', password); 
    let request = this.http.post(Constants.LOGIN_URL, body, null) 
    .do(res => { 
     if (res.status == 200) { 
     this.authTokenService.setAuthToken(res.json().auth_token); 
     } 
    }) 
    .map(res => res.status) 
    .catch(err => { 
     return Observable.throw(err.status) 
    }).publish(); 
    request.connect(); 
    // type assertion because nobody needs to know it is a ConnectableObservable 
    return request as Observable <number> ; 
} 

正如@Maximus所述。如果訂閱發生在ajax調用完成後,您將不會收到結果通知。要處理這種情況,您可以使用publishReplay(1)而不是簡單的publish()PublishReplay(n)將重複由源Observable向新訂戶發出的最後的第n個元素。

+0

你將有一個新的請求,每個電話登錄 –

+0

這不是OP的問題。 (順便說一句:你的解決方案也是如此。) – n00dl3

+0

不,我的解決方案不會每次發送請求。這就是爲什麼我使用變量'sent'。 –

1

在我的回答下面我假設你習慣了通過PromisesHTTP以前的版本中AngularJS提供行爲:

login(username: string, password: string): Observable<number> { 
    return this.http.post(Constants.LOGIN_URL, body, null) 
     .then(res => { 
     if (res.status == 200) { 
      this.authTokenService.setAuthToken(res.json().auth_token); 
     } 
     }) 
     .then(res => res.status) 
     .catch(err => { return Observable.throw(err.status) }); 

可觀察到的從this.http.get()調用返回的是cold觀察到。這意味着除非有人訂閱它,否則它不會開始做任何事情。這是設計。由於沒有訂閱,所有鏈接到返回的observable的操作員都沒有做任何事情。

您需要訂閱才能提出請求,然後與未來的訂閱者分享結果。我認爲AsyncSubject是一個很好的選擇在這裏:

sent = false; 
    s = new AsyncSubject(); 

    login(username: string, password: string): Observable<number> { 
    if (!this.sent) { 
     this.http.post(Constants.LOGIN_URL, body, null) 
     .do(res => { 
      if (res.status == 200) { 
      this.authTokenService.setAuthToken(res.json().auth_token); 
      } 
     }) 
     .map(res => res.status) 
     .catch(err => { return Observable.throw(err.status) }) 
     .subscribe(s); 

     this.sent = true; 
    } 

    return s; 
    } 

這樣只有一個HTTP的通話將被製成,所有的運營商包括do將只運行一次。之後,返回的結果將被緩存在AsyncSubject中,並將其傳遞給所有未來的訂戶。

+0

downvoter謹慎解釋? –

+0

你不需要一個主題。另外你的代碼是錯誤的。如何將一個常量設置爲'false'可以成爲'true'? – n00dl3

+1

不知道爲什麼downvote,這真的解決了他的問題。儘管我仍然很難理解這個問題是什麼。 – PierreDuc