Search code examples
javascriptrxjsredux-observable

Why we need to return a stream instead of direct dispatch a value?


On workspace, my colleagues told me that i must instead of writing this

import store from './store.ts'

// Epic code
...
mergeMap(data => {
    store.dispatch(someActionCreator(data))
    return EMPTY
})
...

should write something like this

// Epic code
...
mergeMap(data => {
    return of(someActionCreator(data))
})
...

I know that all actions which are written in of rxjs operator will be automatic wrapped in dispatch function and dispatched automatically, but why it is bad to dispatch all actions like in the first example?

Yes, we will not return any stream$, but if its a last iteration in sequence, do we really need to return this stream$ instead of manual dispatch?


Solution

  • It is considered a best-practice to not cause "side-effects" in most operators. Side-effects can be described as statements that modify / mutate state somewhere "outside" of the Operator chain.

    With the dispatch call, you directly cause the mutation of state, which can be considered a side-effect.

    There are many opinions why side-effects should be avoided. I can cite a few:

    • Pureness: Operators should always produce the same output for the same input, which includes changes to outside state. By keeping operators pure, they can be properly unit-tested and you can guarantee consistent behavior in all usages of the operator.
    • Reusability: Operators should be designed so that, in principle, they can be extracted and reused somewhere else. This means that they should have no external dependencies such as the store directly in your operator.
    • Convention: Operators should be considered a collection of functions that modify one or many incoming streams of data. Any "reaction" to the results of this stream manipulation should be done in the "subscribe". This is a convention that helps to make Observables behave consistently. Phrased differently: When someone subscribes to your Observable, they should be able to expect no side-effects. In the case of redux, that "someone" would be the framework.
    • Declarative paradigm: Operators should mostly describe the relationship between input and output and not include "instructions" or statements, such as "do this, then do that". That is a paradigm that is considered to reduce errors overall because it removes dependencies to the underlying state of the system.

    By the way, the origin of the whole operator-pattern is functional programming, where a call such as the one you're describing would technically not be possible.

    So I think the answer to your question is mostly opinion-driven. Technically I believe it won't change anything in your use-case. Also, the points made above should be taken with a grain of salt, because you'll always cause side-effects somewhere if you subscribe to e.g. a HTTP API in mergeMap.