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 many 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 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 
takeis liketake+await. - Sagas 
takeMaybe- is liketakeWITHOUTawait. - Sagas 
takeEvery- is likeanAtom.onChange/anAction.onCall. - Sagas 
takeLatest- is likeanAtom.onChange/anAction.onCall+reatomAsync().pipe(withAbort({ strategy: 'last-in-win' })). - Sagas 
takeLeading- is likeanAtom.onChange+reatomAsync().pipe(withAbort({ strategy: 'first-in-win' })). - Sagas 
callis a regular function call with a context +await. - Sagas 
forkis a regular function call with a context WITHOUTawait. - Sagas 
spawnhave no analogy in Reatom. It should create a context without parent context abort propagation. Work in progress. - Sagas 
join- is justawaitin Reatom. - Sagas 
cancelhave no analogy in Reatom. It probably should looks likegetTopController(ctx).abort(). - Sagas 
cancelled- is likeonCtxAbort. 
Two important notes.
- Abortable context in Reatom currently works (starts) only by 
reatomAsyncandonConnect. We will add a new general primitive for that in this package in the nearest time. - A sagas reacts to a [deep] child’s failure, which Reatom doesn’t do. Built-in transaction primitive in a plan.
 
API
take
Allow you to wait an atom update.
import { take } from '@reatom/effects'
const currentCount = ctx.get(countAtom)
const nextCount = await take(ctx, countAtom)
You could await actions too!
// ~/features/someForm.ts
import { take } from '@reatom/effects'
import { onConnect } from '@reatom/hooks'
import { historyAtom } from '@reatom/npm-history'
import { confirmModalAtom } from '~/features/modal'
// some model logic, doesn't matter
export const formAtom = reatomForm(/* ... */)
onConnect(form, (ctx) => {
  // "history" docs: https://github.com/remix-run/history/blob/main/docs/blocking-transitions.md
  const unblock = historyAtom.block(ctx, async ({ retry }) => {
    if (!ctx.get(formAtom).isSubmitted && !ctx.get(confirmModalAtom).opened) {
      confirmModalAtom.open(ctx, 'Are you sure want to leave?')
      const confirmed = await take(ctx, confirmModalAtom.close)
      if (confirmed) {
        unblock()
        retry()
      }
    }
  })
})
takeNested
Allow you to wait all dependent effects, event if they was called in the nested async effect.
For example, we have a routing logic for SSR.
// ~/features/some.ts
import { historyAtom } from '@reatom/npm-history'
historyAtom.locationAtom.onChange((ctx, location) => {
  if (location.pathname === '/some') {
    fetchSomeData(ctx, location.search)
  }
})
How to track fetchSomeData call? We could use takeNested for this.
// SSR prerender
await takeNested(ctx, (trackedCtx) => {
  historyAtom.push(trackedCtx, req.url)
})
render()
You could pass an arguments in the rest params of takeNested function to pass it to the effect.
await takeNested(ctx, historyAtom.push, req.url)
render()
onCtxAbort
Handle an abort signal from a 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.
import { action } from '@reatom/core'
import { reatomAsync, withAbort } from '@reatom/async'
import { onCtxAbort } from '@reatom/effects'
const doLongImportantAsyncWork = action((ctx) =>
  ctx.schedule(() => {
    const timeoutId = setTimeout(() => {
      /* ... */
    })
    onCtxAbort(ctx, () => clearTimeout(timeoutId))
  }),
)
export const handleImportantWork = reatomAsync((ctx) => {
  /* ... */
  doLongImportantAsyncWork(ctx)
  /* ... */
}).pipe(withAbort())