You signed in with another tab or window. Reload to refresh your session.You signed out in another tab or window. Reload to refresh your session.You switched accounts on another tab or window. Reload to refresh your session.Dismiss alert
My team only recently found out that current take operator is implemented in a way that makes it work like a Lazy take operator in terms of unsubscription / completion.
What do I mean by eager and lazy?
The current implementation is lazy because it only completes after the final value has been next'ed (think e.g. take(1), the first and only value we expect to be next'ed will also be the final value being next'ed).
// because re-entrant code will increment `seen` twice.
if(count<=seen){
subscriber.complete();
}
}
subscriber.next() is called on line 60
only afterwards will subscriber.complete() be called (line 65)
In contrast, one could say that take would eagerly unsubscribe, if the completion of the source would happen before nexting the final value. The following can be thought of as being an eager version of the take operator:
if ++seen < count then do just subscriber.next()
else do
source.unsubscribe()
subscriber.next()
subscriber.complete()
Advantages / Disadvantages
Terminology:
when we have pipe(tap(), take(1)), the tap() happens further up the take operator
when we have pipe(take(1), tap()), the tap() happens further down the take operator
lazy take operator
lazy take operator is the current implementation, thus it "works as people expect it to work"
lazy take operator keeps expectation intact that completion further down the pipe/stream bubbles up, stepping over take operator without being interrupted by the take operator
lazy take operator has the disadvantage that the final emission can trigger another source emission, stopping only at the take operator, thus executing all code further up the take operator (of course take will not let through whatever result passes through). However, this lead to a bug in our application because we didn't expect this to happen. In general, it can be described as a problem happening as soon as side effects are involved. In our case, we navigated further up the take(1) and didn't expect that code to be executed again. Even in those cases, where there is no bug because of missing side effects, it still unnecessarily executes code to no effect.
eager take operator
eager take operator does not have the described problems in terms of side effects: the source would be unsubscribed BEFORE the value would be next'ed. Thus, it cannot happen any more that the source emits another time as a result of the value being nexted.
eager take operator would have improved performance: sometimes it can save unnecessary computation that leads to no effect
eager take operator would break people relying on the behavior of lazy take operator allowing the source to emit again to execute wanted side effects one last time before unsubscribing (is this really a legit use case?).
eager take operator maybe could surprise people regarding when and how finalize() calls are executed. Think for example of having some finalize() calls further up the take operator and some additional finalize() calls further down. The order of execution wil be like this: (1) finalize() callbacks further up the take operator are executed, (2) final item is next'ed by eager take operator, (3) finalize() callbacks further down the take operator are executed
Why am I sharing this with you?
I'm interested in what you think about this distinction!
Did you ever have problems regarding take() operator allowing the source to emit again before completion?
How would you name the eager take operator to distinguish it from the existing (lazy) take operator
reacted with thumbs up emoji reacted with thumbs down emoji reacted with laugh emoji reacted with hooray emoji reacted with confused emoji reacted with heart emoji reacted with rocket emoji reacted with eyes emoji
-
My team only recently found out that current take operator is implemented in a way that makes it work like a Lazy take operator in terms of unsubscription / completion.
What do I mean by eager and lazy?
The current implementation is lazy because it only completes after the final value has been next'ed (think e.g.
take(1)
, the first and only value we expect to be next'ed will also be the final value being next'ed).This can easily be seen by looking at the code:
rxjs/src/internal/operators/take.ts
Lines 59 to 67 in 718be5b
subscriber.next()
is called on line 60subscriber.complete()
be called (line 65)In contrast, one could say that
take
would eagerly unsubscribe, if the completion of the source would happen before nexting the final value. The following can be thought of as being an eager version of the take operator:++seen < count
then do justsubscriber.next()
source.unsubscribe()
subscriber.next()
subscriber.complete()
Advantages / Disadvantages
Terminology:
pipe(tap(), take(1))
, thetap()
happens further up the take operatorpipe(take(1), tap())
, thetap()
happens further down the take operatorlazy take operator
take(1)
and didn't expect that code to be executed again. Even in those cases, where there is no bug because of missing side effects, it still unnecessarily executes code to no effect.eager take operator
finalize()
calls are executed. Think for example of having somefinalize()
calls further up the take operator and some additionalfinalize()
calls further down. The order of execution wil be like this: (1)finalize()
callbacks further up the take operator are executed, (2) final item is next'ed by eager take operator, (3)finalize()
callbacks further down the take operator are executedWhy am I sharing this with you?
take()
operator allowing the source to emit again before completion?Beta Was this translation helpful? Give feedback.
All reactions