上面是一个在 Effect
里应用 SwitchMap 的例子:从购物车里移除某个行我的项目
@Effect()
public removeFromCart = this.actions.pipe(ofType(CartActionTypes.RemoveFromCart),
switchMap(action => this.backend
.removeFromCart(action.payload)
.pipe(map(response => new RemoveFromCartFulfilled(response)),
catchError(error => of(new RemoveFromCartRejected(error)))
)
)
);
购物车列出了用户打算购买的商品,每个商品都有一个从购物车中删除商品的按钮。单击该按钮会将 RemoveFromCart 操作分派给与应用程序后端通信的对应 API,并查看从购物车中删除的我的项目。
这段代码看似可能失常运行,但实际上 switchMap 的应用,引入了竞态条件(race condition)。
如果用户单击购物车中多个我的项目的删除按钮,会呈现什么样的行为?
依据客户点击按钮的速度不同,应用程序可能会:
- 从购物车中删除所有点击的物品,比方客户点击一个行我的项目的删除按钮,等删除操作在后盾胜利执行之后,再点击第二个行我的项目。
- 客户飞快地点击了前两个行我的项目的删除按钮。第一个行我的项目的删除申请正在发送往后台服务器的过程当中,则第二个按钮的点击,会勾销第一个行我的项目的删除申请。最初仅仅第二个行我的项目被删除了。
- 客户顺次点击了前两个行我的项目的删除按钮。第一个删除申请曾经到达后盾,正在执行后盾的删除操作。第二个申请也达到了后盾。此时的行为,取决于后盾 API 从 cart 上删除行我的项目时,是否给以后的 cart 加了锁。
咱们考虑一下是否能用如下的 Operator 来代替 SwitchMap.
mergeMap/flatMap
如果 switchMap 被 mergeMap 替换,则 effect 的代码将同时解决每个调度的动作。
也就是说,pending 的删除不会被停止;后端申请将同时产生。申请实现时,Effect 会 dispatch 对应的 action.
须要留神的是,因为操作的并发解决,响应的程序可能与申请的程序不匹配。例如,如果用户单击第一个和第二个我的项目的删除按钮,则第二个我的项目的删除可能产生在第一个我的项目的删除之前。
对于购物车里删除行我的项目的场景而言,删除的程序并不重要,因而应用 mergeMap 而不是 switchMap 能够修复该谬误,躲避潜在的竟态条件。
concatMap
从购物车中移除商品的程序可能无关紧要,但通常有一些操作对排序很重要。
例如,如果咱们的购物车有一个减少商品数量的按钮,那么以正确的程序解决分派的操作很重要。否则,前端购物车中的数量最终可能与后端购物车中的数量不同步。
对于排序很重要的操作,应应用 concatMap.
concatMap 相当于应用并发为 1 的 mergeMap. 也就是说,应用 concatMap 的 effect 代码一次将只解决一个后端申请,并且操作依照它们被调度的程序排队。
concatMap 是一个平安而激进的抉择。当不确定在 Effect 中应用 SwitchMap,MergeMap 或者 concatMap 时,应用 concatMap 比拟平安。
switchMap
每当调度雷同类型的操作时,应用 switchMap 将看到挂起的后端申请停止。这使得 switchMap 对于创立、更新和删除操作不平安。然而,它也可能为读取操作引入谬误。
switchMap 是否实用于特定的读取操作取决于在分派另一个雷同类型的操作后是否仍须要后端响应。让咱们看一下应用 switchMap 会引入谬误的操作。
如果咱们购物车中的每个商品都有一个详细信息按钮——用于显示一些内联详细信息——并且解决详细信息操作的成果 / 史诗应用 switchMap,则引入了竞争条件。如果用户点击了几个我的项目的详细信息按钮,是否显示这些我的项目的详细信息取决于用户点击按钮的速度。
与 RemoveFromCart 操作一样,应用 mergeMap 能够修复谬误。
switchMap 应该只在成果 / 史诗中用于读取操作,并且仅在分派另一个雷同类型的操作后不须要后端响应时应用。
让咱们看一下一个实用的 switchMap 应用场景。
如果咱们的应用程序的购物车显示商品的总成本加上运费,那么对购物车内容的每次更改之后,都会触发一个 GetCartTotal 的读操作。
将 switchMap 用于解决 GetCartTotal 操作的做法是齐全适合的。
如果 Effect 正在解决 GetCartTotal 操作时更改了购物车,则对 pending 申请的响应曾经是古老的 – 它是更改之前购物车中我的项目的总数 – 因而停止挂起的读操作申请是正当的。
事实上,停止这个不必要的读申请比容许该读申请实现而后疏忽——或者更蹩脚的是,在界面上显示古老的响应更可取。
exhaustMap
exhaustMap 可能是最不为人所知的 flatterning 运算符,但它很容易解释:它能够被认为是 switchMap 的背面。
如果应用 switchMap,pending 的后端申请将被停止,以反对最近一次散发的操作。
反之,如果应用了 exhaustMap,当有一个挂起的后端申请时,分派的动作将被疏忽。
开发人员应该相熟一种非凡类型的用户:偏向于一直反复点击同一个按钮。特地是当一直地点击一个按钮并且没有任何响应时,这些用户会再次点击它。
如果购物车有一个刷新按钮,并且解决刷新的 Effect 代码中应用 switchMap,则每次一直的按钮单击都会停止之前触发的刷新操作。
如果解决购物车刷新的 Effect 改为 ExhaustMap,则待处理的刷新申请将反过来疏忽一直反复的点击。
总结
- 将 concatMap 与既不应停止也不应疏忽,
必须保留其程序
的操作一起应用。应用concatMap
是一种激进的抉择,将始终以可预测的形式运行; - 将 mergeMap 与既不应该停止,也不应该疏忽,并且先后顺序不重要的动作一起应用;
- 将 switchMap 与读取操作一起应用,当分派另一个雷同类型的操作时,之前的操作应该被停止,这种状况是 switchMap 的最佳实用场合。
- 如果存在雷同类型的操作处于待处理状态时,新触发的雷同类型的操作应该被疏忽,此时应该是一 exhaustMap.