你給出的工具函數意在從 Observable 同步抓取一個值:
import { Observable } from 'rxjs';
/**
* Will grab last synchronously available value from the observable stream
* at the time of the call.
*/
export function getLastValueSync<T>(source: Observable<T>): T | undefined {
let value: T | undefined;
source.subscribe((emission) => (value = emission)).unsubscribe();
return value;
}
圍繞這段代碼,有幾個關鍵問題值得逐條推演與驗證。
行為剖析:同步流與異步流的分水嶺 Observable 的發出時機既可能是同步,也可能是異步,取決於數據源與調度器。rxjs.dev 的指南明確指出,observable.subscribe() 會以同步或異步的方式推送任意數量的值;subscribe 返回的是一個 Subscription,可用於後續取消訂閲。(rxjs.dev) 在 Angular 指南里也能看到相同的表述:Observable 可以同步發出第一個值,也可以異步地隨時間發出多次。(angular.io)
基於這一本質,getLastValueSync 在不同來源下會呈現出完全不同的結果:
同步數據源的情形
使用 of(1,2,3) 這類同步工廠創建的流,會在調用 subscribe 的過程中立刻把所有值推送完畢,此時 value 會被依次覆蓋並最終等於 3,隨後鏈式調用的 unsubscribe 基本無事可做。Angular 官方文檔明確寫到 of(...values) 會同步交付提供的值。(angular.io)
異步數據源的情形
例如 timer(0)、fromEvent、或 Angular HttpClient.get 這類冷流,首個值在未來的任務隊列裏才會到達。你的代碼在 subscribe 結束後立刻 unsubscribe,在異步值到達前就提前取消了訂閲,value 仍是 undefined。fromEvent 是典型的異步事件源,HttpClient 返回的流也是冷的、惰性的,請求從訂閲時才真正發起。(rxjs.dev)
取消請求的副作用
對 HttpClient 流,提早 unsubscribe 會直接中止正在進行的網絡請求,Angular 新版官方文檔明確寫到,退訂會中止進行中的請求。你的函數在異步場景下不僅拿不到值,還會把請求取消。(Angular)
設計層面的隱患清單
-
不確定性 這個工具的結果依賴於上游是否同步發出。同一個簽名的 Observable,有的實現同步,有的實現異步;換個操作符如 observeOn 就能改變行為。這讓函數的可預期性很差。關於同步與異步發出,官方指南與社區討論都強調取決於數據源與運算鏈。(rxjs.dev)
-
錯誤未處理 subscribe 只提供了 next 回調,沒有傳入 error 與 complete。若上游同步拋錯,錯誤會直接冒泡到宿主環境,調用方完全不知情。Observable 的三個通知通道在文檔裏描述得很清楚。(rxjs.dev)
-
違反組合式思維 在 rxjs 裏更推崇用運算符組合流,而不是在任意位置臨時訂閲再取值。文檔把運算符定義為純函數式的管道轉換,這有利於可測試與可推理。(rxjs.dev)
-
觸發多餘副作用 很多 Observable 是冷流,訂閲就會觸發真實的副作用,例如 HttpClient 發起一次網絡請求。你的函數每調用一次就會訂閲一次,既可能白跑請求,又可能因為立即退訂把請求取消。(bennadel.com)
-
可讀性與意圖表達 函數名叫 getLastValueSync,但它拿到的只是當前同步階段內最後一次 next 的值,並非真正意義上的 last 值。對無限流而言根本不存在最後一個值。