原文来源: https://blog.angularindepth.com
翻译说明: 中英对照, 意翻, 节选重要部分, 想了解原文的朋友可以去博客地址查找该文。
Let’s use a shopping cart as an example and have a look at an effect — and an epic — that misuses switchMap
and then consider some alternative operators.
让我们用购物车作为例子, 看一下 滥用 switchMap
的effect 以及 epic , 然后考虑一下一些可以替代的操作符。
Here’s an NgRx effect that misuses switchMap
:
这是个 滥用 switchMap
的 NgRx effect:
@Effect()
public removeFromCart = this.actions.pipe(
ofType(CartActionTypes.RemoveFromCart),
switchMap(action => this.backend
.removeFromCarg(action.payload)
.pipe(
map(response => new RemoveFromCartFullfilled(response)),
catchError(error => of(new RemoveFromCartRejected(error)))
)
)
);
And here’s its equivalent redux-observable epic:
这是和它同样的 redux-observable epic:
const removeFromCart = actions$ => actions$.pipe(
ofType(actions.REMOVE_FROM_CART),
switchMap(action => backend
.removeFromCart(action.payload)
.pipe(
map(response => actions.removeFromCartFullfilled(response)),
catchError(error => of(new RemoveFromCartRejected(error)))
)
)
);
Our shopping cart lists the items the user intends to purchase and against each item is a button that removes the item from the cart.
我们的购物车列出了用户要购买的商品, 每件商品都有个删除按钮。
Clicking the button dispatches a RemoveFromCart
action to the effect/epic which communicates with the application’s backend and sees the item removed from the cart.
点击该按钮会发送 RemoveFromCart
action 到 effect/epic, effect/epic 会和应用程序的后端进行交互, 你会看到商品从购物车中删除。
Most of the time, this will function as intended. However, the use of switchMap
has introduced a race condition.
大多数情况下, 这将按预期进行。 然而, 使用 switchMap
会引入竞争条件。
If the user clicks the remove buttons for several items in the cart, what happens depends upon how rapidly the buttons are clicked.
如果用户点击了购物车中几件商品的删除按钮, 会发生什么取决于按钮的点击速度。
If a remove button is clicked when the effect/epic is communicating with the backend — that is, when a removal is pending — the use of switchMap
will see the pending removal aborted.
如果在 effect/epic 和后端进行交互时点击了删除按钮——也就是, 当某个删除操作正在等待中时—— 使用switchMap
, 会中止等待中的 删除操作。
So, depending upon how rapidly the buttons are clicked, the application might:
因此, 取决于按钮的点击速度, 应用程序可能:
remove all of the clicked items from the cart;
从购物车中删除所有点击的商品remove only some of the clicked items from the cart; or
从购物车中只删除一部分点击的商品; 或者remove some of the clicked items from the backend’s cart but not reflect their removal from the frontend’s cart.
从后端的购物车中删除一部分点击的商品, 但是在前端购物车中并没有反映出来。
Clearly, this is a bug.
很明显,这是个bug。
It’s unfortunate that switchMap
is often suggested as the first choice when a flattening operator is required, as it’s not safe for all scenarios.
不幸的是,当需要平化操作符时,switchMap
通常被认为是首选,但其对于所有的场景并不都是安全的。
RxJS has four flattening operators to choose from:
RxJS 有4种可以选择的平化操作符:
-
mergeMap
(also known asflatMap
) concatMap
switchMap
exhaustMap
Let’s have a look at these operators to see how they differ and to determine the shopping-cart scenarios for which each operator is best suited.
我们看一下这些操作符,看看它们是如何不同的,并确定每个操作符最适合的购物车场景。
mergeMap/flatMap
If switchMap
is replaced with mergeMap
, the effect/epic will concurrently handle each dispatched action.
如果将 switchMap
替换成 mergeMap
, effect/epic 会并行处理每个发出的 action
That is, pending removals will not be aborted; the backend requests will occur concurrently and, when fulfilled, the response actions will be dispatched.
也就是说, 等待中的删除操作不会被中止; 后端请求会同时发生, 当完成后, 响应 actions 会发出。
It’s important to note that due to the concurrent handling of the actions, the order of the responses might not match the order of the requests.
需要注意的是, 由于并行处理actions, 响应的顺序可能和请求的顺序不匹配。
For example, if the user clicks the remove buttons of the first and second items, it’s possible that the removal of the second item might occur before the removal of the first.
举个例子, 如果用户点击了第一件商品和第二件商品的删除按钮, 可能第二件商品的删除要在第一件商品的删除之前发生。
With our cart, the ordering of the removals doesn’t matter, so using mergeMap
instead of switchMap
fixes the bug.
对于我们的购物车, 删除的顺序并不重要, 所以使用 mergeMap
取代 switchMap
修复了这个bug。
concatMap
The order in which items are removed from the cart might not matter, but there are often actions for which the ordering is important.
从购物车中删除商品的顺序并不重要, 但是也有一些 action 的顺序是重要的。
For example, if our shopping cart has a button for increasing an item’s quantity, it’s important that the dispatched actions are handled in the correct order.
举个例子, 如果我们的购物车有个增加商品数量的按钮, 发出的 action 按顺序处理很重要。
Otherwise, the quantities in the frontend’s cart could end up out-of-sync with the quantities in the backend’s cart.
否则, 前端购物车里的数量最终可能和后端购物车里的数量不同步。
With actions for which the ordering is important, concatMap
should be used.
对于 顺序很重要的 action, 应当使用 concatMap
。
concatMap
is the equivalent of using mergeMap
with a concurrency of one.
concatMap
相当于 使用 并行数为 1 的 mergeMap
。
That is, an effect/epic using concatMap
will be processing only one backend request at a time — and actions are queued in the order in which they are dispatched.
也就是说, 使用 concatMap
的 effect/epic 每次只处理一个后端请求, 并且 actions 会按发送的顺序排队进行。
concatMap
is a safe, conservative choice. If you are unsure of which flattening operator to use in an effect/epic, use concatMap
.
concatMap
是安全的、保守的选择。 如果在 effect/epic 中你不知道要用哪个平化操作符, 那么使用concatMap
switchMap
The use of switchMap
will see pending backend requests aborted whenever an action of the same type is dispatched.
当发送相同类型的 action 时,使用 switchMap
, 将会中止待处理的后端请求。
That makes switchMap
unsafe for create, update and delete actions. However, it can also introduce bugs for read actions.
把 switchMap
用于 创建、 更新 以及删除 action 将是不安全的。 然而, 把它用于 读取 action 同样会引入bug。
Whether or not switchMap
is appropriate for a particular read action depends upon whether or not the backend response is still required after another action of the same type is dispatched.
把 switchMap
用于某个特定的读取 action 是否合适, 取决于另一个同样类型的action发出后,后端响应是否仍然是需要的。
Let’s look at an action with which the use of switchMap
would introduce a bug.
我们来看一下使用 switchMap
会引入bug 的一个 action。
If each item in our shopping cart has a details button — for showing some inline details — and the effect/epic that handles the details action uses switchMap
, a race condition is introduced.
如果在我们的购物车中每一件商品都有个详情按钮 —— 用于展示一些细节—— 处理 详情 action 的 effect/epic 使用了 switchMap
, 引入了竞争条件。
If the user clicks the details buttons of several items, whether or not the details for those items will be displayed depends upon how rapidly the user clicks the buttons.
如果用户多次点击了详情按钮, 这些商品细节的显示与否取决于用户以多快的速度点击按钮。
As with the RemoveFromCart
action, using mergeMap
would fix the bug.
对于 RemoveFromCart
action, 使用 mergeMap
会修复这个bug。
switchMap
should only be used in effects/epics for read actions and only when the backend response is not required after another action of the same type is dispatched.
在 effect/epics 中, switchMap
应当只用于 读取 action, 并且只应当用于 当另一个同样类型的action 发出后, 后端响应不再需要的情况。
Let’s look at a scenario in which switchMap
would be useful.
我们来看一下switchMap
会非常有用一个场景。
If our application has a GetCart
action that sees a request made to the backend for the cart’s contents whenever the user navigates to the cart, it makes sense to use switchMap
.
如果我们的应用程序有 GetCart
action, 无论何时, 只要用户导航到购物车, 就会发送查询购物车内容的请求到后端。
Then, if the user moves back and forth between the cart and the store, only the most recently dispatched GetCart
action will be handled — previous backend requests for the cart’s contents will be aborted.
然后, 如果用户在购物车和商店之间来回变动, 只有最近发出的 GetCart
action 才会被处理——之前向后端发出查询购物车内容的请求会被中止。
exhaustMap
exhaustMap
is perhaps the least-well-known of the flattening operators, but it’s easily explained: it can be thought of as the opposite of switchMap
.
exhaustMap
或许是最不出名的平化操作符了, 但是它很容易解释: 可以把它想成 switchMap
的相反操作。
If switchMap
is used, pending backend requests are aborted in favour of more recently dispatched actions.
如果使用了 switchMap
, 等待中的后端请求会中止, 最新发出的action 会取而代之。
However, if exhaustMap
is used, dispatched actions are ignored whilst there is a pending backend request.
然而, 如果使用了 exhaustMap
, 当有后端请求等待时, 发送的 action 会被忽略。
Let’s look at a scenario in which exhaustMap
could be used.
我们来看一下 exhaustMap
应当使用的场景。
There’s a particular type of user with whom developers should be familiar: the incessant button clicker.
有种特定的用户类型, 开发者应该很熟悉: 不停点击按钮者。
When the incessant button clicker clicks a button and nothing happens, they click it again. And again. And again.
当用户单击按钮 并且什么也没有发生时, 他们会再次单击按钮, 一次又一次地单击。
If our shopping cart has a refresh button and the effect/epic that handles the refresh uses switchMap
, each incessant button click will abort the pending refresh.
如果我们的购物车有个刷新按钮, effect/epic 使用 switchMap
处理刷新, 每次连续的按钮单击会中止等待中的请求
That doesn’t make a whole lot of sense and the incessant button clicker could be clicking for a long, long time before a refresh occurs.
这并没有多大的意义, 用户在刷新发生前可能会单击很多次。
If the effect/epic that handles the refreshing of the cart instead used exhaustMap
, a pending refresh request would see the incessant clicks ignored.
如果 effect/epic 使用 exhaustMap
处理购物车的刷新, 等待中的刷新请求会忽略 连续的点击。
TL;DR
To summarise, when you need to use a flattening operator in an effect/epic you should:
总结一下, 当你需要在 effect/epic 中使用平化操作符时, 你应该:
use
concatMap
with actions that should be neither aborted nor ignored and for which the ordering must be preserved — it’s also a conservative choice that will always behave in a predictable manner;
对 既 不能中止 又 不能忽视 的并且 顺序必须保留 的 actions 使用concatMap
—— 这也同样是一种保守的选择, 其行为总是可预测的。use
mergeMap
with actions that should be neither aborted nor ignored and for which the ordering is unimportant;
对 既 不能中止 又 不能忽视 的并且 顺序不重要 的 actions 使用mergeMap
use
switchMap
with read actions that should be aborted when another action of the same type is dispatched;
对 当又一个同样类型的 action 发送时 , 应当中止的 读 actions 使用switchMap
,use
exhaustMap
with actions that should be ignored whilst an action of the same type is pending.
使用exhaustMap
, 当同样类型的 action 等待时, 其他actions 应当忽略。