首先了解 combineLatest 這個操作符的作用:
組合多個 Observable 以建立一個 Observable,其值是根據其每個輸入 Observable 的最新值計算得出的。
其彈珠圖如下圖所示:
我們有一個限制值流和一個偏移值流。 我們使用 combineLatest 組合這些流以建立一個流,該流將在每次源流之一更改時具有一個新值。 然後我們使用 switchMap 根據這些值從後端獲取資料以獲取 pokemon$。 因為我們使用了switchMap,如果一個呼叫還沒有結束,那麼當一個新的呼叫通過改變limit或者offset來發起一個新的呼叫時,前一個呼叫就會被取消。
程式碼如下:
this.pokemon$ = combineLatest(limit$, offset$)
.pipe(
map(data => ({limit: data[0], offset: data[1]})),
switchMap(data => this.pokemonService.getPokemon(data.limit, data.offset)),
map((response: {results: Pokemon[]}) => response.results),
);
程式碼地址如下:
https://stackblitz.com/edit/a...
當我修改 limit 和 offset 為其他值之後,點選 reset 按鈕,此時會觀察到先後發起兩個請求,並且第一個請求自動被 cancel 的情況:
通過單擊重置按鈕,我們通過同時重置限制和偏移值來更新我們的兩個源流。 這個動作的效果是 combineLatest 建立的流觸發了兩次,因此啟動了兩個後端請求,另一方面,由於我們使用了 switchMap,立即取消了一個。
我們來單步拆解,以加深印象:
- combineLatest 儲存所有源流的最後一個值。比如開始場景是,limit = 8,offset = 2)
- 單擊重置按鈕
- limit 設定為 5
- combineLatest 看到一個新值進入 limit 併發出一個新組合,limit = 5,offset = 2
- switchMap 獲取這些值並訂閱觸發後端呼叫的流
偏移量設定為 0 - combineLatest 看到一個新的 offset 值,併發出一個新的組合,limit = 5,offset = 0
- switchMap 獲取這些值,取消訂閱(並因此取消)先前的請求並開始新的請求
在此流程中您可能沒有預料到的是,無論何時設定 limit ,此更改都會在更改 offset 之前直接傳播到 combineLatest.
如何避免這個行為
如果有一種方法可以確保在同一個呼叫堆疊中發生的更改(這是單擊重置按鈕時發生的情況)被丟棄以支援最後一次更改,我們可以解決我們的問題。
這意味著,當 combineLatest 在同一個呼叫堆疊中發出兩個值時,當呼叫堆疊被清除時,最後一個值將被髮送。
為此,我們可以在 combineLatest 之後直接利用值為 0 的 debounceTime。 這將確保只有最後一個值被傳遞給 switchMap,並且在呼叫堆疊被清除之後。
每當提到“在同一個呼叫堆疊中”時,都可以將其替換為“在事件迴圈的同一輪次中發生的更改”。
加上了 debounceTime(0) 之後的時序圖:
- combineLatest 儲存所有源流的最後一個值,開始場景是,limit = 8,offset = 2
- 單擊重置按鈕
- 限制設定為 5
- combineLatest 運算子看到一個新值進入 limit 併發出一個新組合,limit = 5,offset = 2
- debounceTime 運算子看到一個新值,並且(因為操作符的值為 0)將等待直到呼叫堆疊被清除以將其傳遞
- 偏移量設定為 0
- combineLatest 運算子看到一個新的 offset 值,併發出一個新的組合,limit = 5,offset = 0
- debounceTime 運算子再次看到一個新值,將丟棄舊值,並等待堆疊被清除以將其傳遞
- 呼叫堆疊被清除
- debounceTime 運算子沒有看到新的值,將通過組合,limit = 5,offset = 0 向下遊傳送資料
- switchMap 操作符獲取這些值並訂閱觸發後端呼叫的流
修復程式碼非常簡單,加上一行程式碼即可:
debounceTime(0),
修復後的效果,點選 reset 按鈕之後,只有一次 HTTP 請求發出了:
更多Jerry的原創文章,盡在:"汪子熙":