effects
This package is inspired by Sagas and gives you advanced effect management solutions.
included in @reatom/framework
First of all you should know that some effects and async (reatom/async + reatom/hooks) logic uses AbortController under the hood and if some of the controller aborted all nested effects will aborted too! It is a powerful feature for managing async logic which allows you to easily write concurrent logic, like with redux-saga or rxjs, but with the simpler native API.
Before we start, you could find a lot of useful helpers to manage aborts in reatom/utils
The differences between Redux-Saga and Reatom.
#- Sagas
take
is liketake
+await
. - Sagas
takeMaybe
- is liketake
WITHOUTawait
. - Sagas
takeEvery
- is likeanAtom.onChange
/anAction.onCall
. - Sagas
takeLatest
- is likeanAtom.onChange
/anAction.onCall
+concurrent
. - Sagas
takeLeading
- is likeanAtom.onChange
+reatomAsync().pipe(withAbort({ strategy: 'first-in-win' }))
. - Sagas
call
is a regular function call with a context +await
. - Sagas
fork
is a regular function call with a context WITHOUTawait
. - Sagas
spawn
isspawn
- Sagas
join
- is justawait
in Reatom. - Sagas
cancel
is likegetTopController(ctx.cause)?.abort()
. - Sagas
cancelled
- is likeonCtxAbort
.
API
#concurrent
#This is the basic, useful API for performing concurrent async logic. Wrap your function with the concurrent
decorator, and all scheduled tasks of the passed ctx
will throw the abort error when a new request appears.
Main use case for the concurrent API is onChange
handling. Just wrap your function to always get only fresh results, no matter how often the changes occur.
Here, when someAtom
changes for the first time, the hook will be called and start fetching. If someAtom
changes during the fetch execution, the ctx.schedule
of the previous (first) call will throw an AbortError
, and the new fetching will start.
Another example is how easily you could implement the “debounce” pattern with additional logic. Here is a comparison of the classic “debounce” decorator from “lodash” or any other utility library with the concurrent API. Each of the three examples has the same behavior for the debounce and concurrent examples.
You can see that each new logic addition forces a lot of changes for code with the simple debounce decorator and takes a really small amount of changes for code with the concurrent decorator.
Base debounce.
Debounce after some mappings
Debounce with a condition.
take
#This is the simplest and most powerful API that allows you to wait for an atom update, which is useful for describing certain procedures. It is a shortcut for subscribing to the atom and unsubscribing after the first update. take
respects the main Reatom abort context and will throw AbortError
when the abort occurs. This allows you to describe redux-saga-like procedural logic in synchronous code style with native async/await.
You can also await actions!
take checkpoints
#But be aware that take
only starts listening when it’s called:
You can fix this bug by creating a checkpoint before starting any process:
take filter
#You can pass the third argument to map the update to the required format.
More than that, you can filter unneeded updates by returning the skip
mark from the first argument of your callback.
The cool feature of this skip mark is that it helps TypeScript understand the correct type of the returned value, which is hard to achieve with the extra “filter” function. If you have a union type, you could receive the needed data with the correct type easily. It just works.
takeNested
#Allow you to wait all dependent effects, event if they was called in the nested async effect or by spawn.
For example, we have a routing logic for SSR.
How to track fetchSomeData
call? We could use takeNested
for this.
You could pass an arguments in the rest params of takeNested
function to pass it to the effect.
onCtxAbort
#Handle an abort signal from the cause stack. For example, if you want to separate a task from the body of the concurrent handler, you can do it without explicit abort management; all tasks are carried out on top of ctx
.
getTopController
#This is a simple util to find an abort controller on top of your cause stack. For example, it is useful to stop some async operation inside a regular actions, which are probably called from a concurrent context.
spawn
#This utility allow you to start a function with context which will NOT follow an abort of the cause.
For example, you want to start a fetch when Atom gets a connection, but don’t want to abort the fetch when the connection is lost. This is because you want to persist the results.