Issue
I am designing the API of a business-logic library. It is similar in goals and approach to Redux-Saga - managing side-effects and change-propagation of a central store while eliminating any Redux or React dependency.
Can anyone detail specific benefits that means redux-saga has to use generators rather than, for example, offering decorators for async functions (that would intercept callArguments and returnValues and make them available for middleware). I need to decide what strategy to use.
Redux-Saga uses synchronous generator functions declared with *
and using yield
to define business logic. This is in contrast to code for side-effects composed from asynchronous Promise-based functions declared with async
and using await
.
In https://github.com/redux-saga/redux-saga/issues/987 Mateusz Burzyński the maintainer of redux-saga states ‘redux-saga cannot be rewritten to async/await’. He goes on ‘Saga being an interpreter of effects is granting us the full control of how and when those effects are resolved’ and ‘we just can't use anything other than generators to reach our goals, because only them are giving us the needed flexibility’.
However, I believe that wrapping async calls could also allow interception of how and when async calls were resolved. I ask myself what did he mean?
So far I have modelled my logic-layer’s ‘sagas’ on the generator-based approach of Redux-Saga and I'm fairly happy with it. However developer feedback so far is basically ‘why are you using yield not await?’ (this is from non redux-saga users who might complain also about redux-saga).
Their view has been that using yield
alienates developers who find generators to be unfamiliar magic compared to async/await
code, so I find myself having to justify the approach and sometimes I struggle.
Functions based on either await
or yield
have a lot in common. In each case steps can be run strictly in sequence, they can delegate to other functions, they ‘pause’ while they wait for the next event and they keep implicit state according to where they are ‘up to’ within the sequential procedure, without having to define any additional state to manage this. I am finding it difficult to articulate clearly what CANNOT be expressed through await
and CAN ONLY be expressed through yield
to justify the initial alignment of my API with redux-saga and generators. Is there anything I can say to justify it?
Under the hood the primitives in my framework already provide async-based implementations for editing and tracking changes in the store (eliminating Redux reducers and redux-saga middleware) and for event queues (fulfilling the need for action-consume patterns like channels and takeLatest). However, the async operations I’ve written have then been wrapped again in ‘Actions’ to present them in the generator layer. This makes me speculate that the generator layer isn’t buying me anything compared to just writing functions using async that directly invoke my async layers. Is there's something fundamental I'm missing?
I am beginning to doubt myself and would like to know others’ views why Redux-Saga uses yield, or what particular power is available through a generator approach as opposed to just writing wrappers around async calls.
So far I can think of two main reasons, which seem weak, given yield
might alienate many mainstream developers, and assuming async can deliver the same goodness.
- Synchronous generator code makes business logic tests easier to write and problem cases easier to log for debugging. You can emulate all kinds of events and side-effects without mocking (see redux-saga-test-plan) since you can intercept every instruction and substitute every value arising! Having said this, mocking is a really mature and familiar technique for javascript test authors. Also if the framework introduced wrappers to intercept the core async functions of your app, this interception could be used for mocking and logging in any case.
- Forcing developers to write logic through synchronous
generators creates an artificial barrier to isolate it from async
code. You can’t even use the async keyword in a saga. Perhaps this
would make people write better business logic separation? Having said
this, if we accepted that ‘wild-west’ async code was a legitimate
starting point, we could progressively introduce the power of sagas
by just wrapping selected async calls for interception, until we had
the level of interception we actually need. E.g. our processes could
block on
await actionMatching()
orawait selectorChange()
to be notified of the initiation or completion of another call in another 'process' or to notice a state transition. This wouldn't need events served up through a generator to enable
processes to block onyield takeLatest()
.
Is there a fundamental constraint that means I need generators to fulfil the capabilities of Redux-Saga?
Solution
Redux Saga cannot be re-written to use async/await
as per the developers of the library - or maybe more accurately, in my understanding, it would be so onerous for the developers of such a library that it's never going to happen.
From the library's (redux-saga) point of view async/await is to generators like a younger, dummy brother wink You can do much, much more with generators than with async/await. -- Source comment
and
redux-saga
cannot be rewritten to async/await - that uses Promises and its way harder to coordinate 'parallel' tasks with those. It would also mean a big change in semantics, how things actually work. In exampleselect
effect is synchronous - cant achieve that with Promises.
Saga being an interpreter of effects is granting us the full control of how and when those effects are resolved + effects are just simple object which are easily comparable and can be monitored which is also way harder with Promises. -- Source comment
Answered By - Slbox
0 comments:
Post a Comment
Note: Only a member of this blog may post a comment.