combineLatest 使用的一個陷阱和基於 debounceTime 的解決方案

i042416發表於2022-02-14

首先了解 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的原創文章,盡在:"汪子熙":

相關文章