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.
await actionMatching()
or await 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 enableyield takeLatest()
.Is there a fundamental constraint that means I need generators to fulfil the capabilities of Redux-Saga?
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