combineLatest 使用的一个陷阱和基于 debounceTime 的解决方案

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

你可能感兴趣的:(combineLatest 使用的一个陷阱和基于 debounceTime 的解决方案)